From c2fcee21b9e21855e0d76ed604139e5826dad7ad Mon Sep 17 00:00:00 2001 From: sirjonasxx <36828922+sirjonasxx@users.noreply.github.com> Date: Tue, 26 Oct 2021 13:14:09 +0200 Subject: [PATCH 01/21] potential bugfix --- .../java/gearth/protocol/HConnection.java | 8 +- .../gearth/protocol/connection/HProxy.java | 22 ++++-- .../connection/PacketSenderQueue.java | 78 ------------------- .../protocol/packethandler/PacketHandler.java | 2 +- .../flash/FlashPacketHandler.java | 8 +- .../unity/UnityPacketHandler.java | 3 +- 6 files changed, 28 insertions(+), 93 deletions(-) delete mode 100644 G-Earth/src/main/java/gearth/protocol/connection/PacketSenderQueue.java diff --git a/G-Earth/src/main/java/gearth/protocol/HConnection.java b/G-Earth/src/main/java/gearth/protocol/HConnection.java index 90029cf..db1c083 100644 --- a/G-Earth/src/main/java/gearth/protocol/HConnection.java +++ b/G-Earth/src/main/java/gearth/protocol/HConnection.java @@ -140,6 +140,7 @@ public class HConnection { public boolean sendToClient(HPacket packet) { + HProxy proxy = this.proxy; if (proxy == null) return false; if (!packet.isPacketComplete()) { @@ -149,10 +150,10 @@ public class HConnection { if (!packet.isPacketComplete() || !packet.canSendToClient()) return false; } - proxy.getPacketSenderQueue().queueToClient(packet); - return true; + return proxy.sendToClient(packet); } public boolean sendToServer(HPacket packet) { + HProxy proxy = this.proxy; if (proxy == null) return false; if (!packet.isPacketComplete()) { @@ -162,8 +163,7 @@ public class HConnection { if (!packet.isPacketComplete() || !packet.canSendToServer()) return false; } - proxy.getPacketSenderQueue().queueToServer(packet); - return true; + return proxy.sendToServer(packet); } public String getClientHost() { diff --git a/G-Earth/src/main/java/gearth/protocol/connection/HProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/HProxy.java index 9b90790..e3bb228 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/HProxy.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/HProxy.java @@ -1,5 +1,6 @@ package gearth.protocol.connection; +import gearth.protocol.HPacket; import gearth.services.packet_info.PacketInfoManager; import gearth.protocol.packethandler.PacketHandler; @@ -25,8 +26,6 @@ public class HProxy { private volatile String clientIdentifier = ""; private volatile PacketInfoManager packetInfoManager = null; - private volatile PacketSenderQueue packetSenderQueue = null; - public HProxy(HClient hClient, String input_domain, String actual_domain, int actual_port, int intercept_port, String intercept_host) { this.hClient = hClient; this.input_domain = input_domain; @@ -46,7 +45,20 @@ public class HProxy { this.hotelVersion = hotelVersion; this.clientIdentifier = clientIdentifier; this.packetInfoManager = PacketInfoManager.fromHotelVersion(hotelVersion, hClient); - this.packetSenderQueue = new PacketSenderQueue(this); + } + + public boolean sendToServer(HPacket packet) { + if (outHandler != null) { + return outHandler.sendToStream(packet.toBytes()); + } + return false; + } + + public boolean sendToClient(HPacket packet) { + if (inHandler != null) { + return inHandler.sendToStream(packet.toBytes()); + } + return false; } public String getClientIdentifier() { @@ -89,10 +101,6 @@ public class HProxy { return hotelVersion; } - public PacketSenderQueue getPacketSenderQueue() { - return packetSenderQueue; - } - public HClient gethClient() { return hClient; } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/PacketSenderQueue.java b/G-Earth/src/main/java/gearth/protocol/connection/PacketSenderQueue.java deleted file mode 100644 index bef2d2b..0000000 --- a/G-Earth/src/main/java/gearth/protocol/connection/PacketSenderQueue.java +++ /dev/null @@ -1,78 +0,0 @@ -package gearth.protocol.connection; - -import gearth.protocol.HPacket; - -import java.util.LinkedList; -import java.util.Queue; - -public class PacketSenderQueue { - - private final HProxy proxy; - private final Queue sendToClientQueue = new LinkedList<>(); - private final Queue sendToServerQueue = new LinkedList<>(); - - PacketSenderQueue(HProxy proxy) { - this.proxy = proxy; - new Thread(() -> { - while (true) { - HPacket packet; - synchronized (sendToClientQueue) { - while ((packet = sendToClientQueue.poll()) != null) { - sendToClient(packet); - } - } - - try { - Thread.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }).start(); - new Thread(() -> { - while (true) { - HPacket packet; - synchronized (sendToServerQueue) { - while ((packet = sendToServerQueue.poll()) != null) { - sendToServer(packet); - } - } - - try { - Thread.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }).start(); - } - - - private void sendToClient(HPacket message) { - proxy.getInHandler().sendToStream(message.toBytes()); - } - private void sendToServer(HPacket message) { - proxy.getOutHandler().sendToStream(message.toBytes()); - } - - public void queueToClient(HPacket message) { - synchronized (sendToClientQueue) { - sendToClientQueue.add(message); - } - - } - public void queueToServer(HPacket message) { - synchronized (sendToServerQueue) { - sendToServerQueue.add(message); - } - } - - public void clear() { - synchronized (sendToClientQueue) { - sendToClientQueue.clear(); - } - synchronized (sendToServerQueue) { - sendToServerQueue.clear(); - } - } -} diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/PacketHandler.java b/G-Earth/src/main/java/gearth/protocol/packethandler/PacketHandler.java index 1336b7d..a0d2677 100644 --- a/G-Earth/src/main/java/gearth/protocol/packethandler/PacketHandler.java +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/PacketHandler.java @@ -20,7 +20,7 @@ public abstract class PacketHandler { } - public abstract void sendToStream(byte[] buffer); + public abstract boolean sendToStream(byte[] buffer); public abstract void act(byte[] buffer) throws IOException; diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/flash/FlashPacketHandler.java b/G-Earth/src/main/java/gearth/protocol/packethandler/flash/FlashPacketHandler.java index 44c2e59..018bca1 100644 --- a/G-Earth/src/main/java/gearth/protocol/packethandler/flash/FlashPacketHandler.java +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/flash/FlashPacketHandler.java @@ -51,7 +51,9 @@ public abstract class FlashPacketHandler extends PacketHandler { public void act(byte[] buffer) throws IOException { if (!isDataStream) { - out.write(buffer); + synchronized (sendLock) { + out.write(buffer); + } return; } @@ -113,7 +115,7 @@ public abstract class FlashPacketHandler extends PacketHandler { isTempBlocked = false; } - public void sendToStream(byte[] buffer) { + public boolean sendToStream(byte[] buffer) { synchronized (sendLock) { try { out.write( @@ -121,8 +123,10 @@ public abstract class FlashPacketHandler extends PacketHandler { ? buffer : encryptcipher.rc4(buffer) ); + return true; } catch (IOException e) { e.printStackTrace(); + return false; } } } diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/unity/UnityPacketHandler.java b/G-Earth/src/main/java/gearth/protocol/packethandler/unity/UnityPacketHandler.java index 2455bbc..6bcca62 100644 --- a/G-Earth/src/main/java/gearth/protocol/packethandler/unity/UnityPacketHandler.java +++ b/G-Earth/src/main/java/gearth/protocol/packethandler/unity/UnityPacketHandler.java @@ -23,11 +23,12 @@ public class UnityPacketHandler extends PacketHandler { } @Override - public void sendToStream(byte[] buffer) { + public boolean sendToStream(byte[] buffer) { byte[] prefix = new byte[]{(direction == HMessage.Direction.TOCLIENT ? ((byte)0) : ((byte)1))}; byte[] combined = ByteArrayUtils.combineByteArrays(prefix, buffer); session.getAsyncRemote().sendBinary(ByteBuffer.wrap(combined)); + return true; } @Override From d9d1785a338323451a0e70f004c1a1a6bc5f8e53 Mon Sep 17 00:00:00 2001 From: sirjonasxx <36828922+sirjonasxx@users.noreply.github.com> Date: Fri, 26 Nov 2021 00:51:46 +0100 Subject: [PATCH 02/21] updated for gwasm minimal 1.0.1 --- G-Earth/pom.xml | 2 +- .../codepatcher/IncomingPacketPatcher.java | 29 +++++++++---------- .../codepatcher/OutgoingPacketPatcher.java | 23 +++++++-------- .../codepatcher/ReturnBytePatcher.java | 15 ++++------ .../codepatcher/SetKeyPatcher.java | 29 +++++++++---------- 5 files changed, 44 insertions(+), 54 deletions(-) diff --git a/G-Earth/pom.xml b/G-Earth/pom.xml index 37b04bb..09b1b80 100644 --- a/G-Earth/pom.xml +++ b/G-Earth/pom.xml @@ -232,7 +232,7 @@ G-Earth G-Wasm - 1.0 + 1.0.1 diff --git a/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/IncomingPacketPatcher.java b/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/IncomingPacketPatcher.java index 48e269b..b3482cd 100644 --- a/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/IncomingPacketPatcher.java +++ b/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/IncomingPacketPatcher.java @@ -7,7 +7,6 @@ import wasm.disassembly.modules.sections.code.Locals; import wasm.disassembly.types.FuncType; import wasm.disassembly.types.ResultType; import wasm.disassembly.types.ValType; -import wasm.misc.CodeCompare; import wasm.misc.StreamReplacement; import java.util.Arrays; @@ -38,23 +37,23 @@ public class IncomingPacketPatcher implements StreamReplacement { } @Override - public CodeCompare getCodeCompare() { - return code -> { - if (!(code.getLocalss().equals(Collections.singletonList(new Locals(1, ValType.I32))))) - return false; + public boolean codeMatches(Func code) { + if (!(code.getLocalss().equals(Collections.singletonList(new Locals(1, ValType.I32))))) + return false; - List expectedExpr = Arrays.asList(InstrType.I32_CONST, InstrType.I32_LOAD8_S, - InstrType.I32_EQZ, InstrType.IF, InstrType.LOCAL_GET, InstrType.I32_LOAD, InstrType.LOCAL_TEE, - InstrType.IF); + List expectedExpr = Arrays.asList(InstrType.I32_CONST, InstrType.I32_LOAD8_S, + InstrType.I32_EQZ, InstrType.IF, InstrType.LOCAL_GET, InstrType.I32_LOAD, InstrType.LOCAL_TEE, + InstrType.IF); - if (code.getExpression().getInstructions().size() != expectedExpr.size()) return false; + if (code.getExpression().getInstructions().size() != expectedExpr.size()) return false; - for (int j = 0; j < code.getExpression().getInstructions().size(); j++) { - Instr instr = code.getExpression().getInstructions().get(j); - if (instr.getInstrType() != expectedExpr.get(j)) return false; - } + for (int j = 0; j < code.getExpression().getInstructions().size(); j++) { + Instr instr = code.getExpression().getInstructions().get(j); + if (instr.getInstrType() != expectedExpr.get(j)) return false; + } - return true; - }; + return true; } + + } diff --git a/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/OutgoingPacketPatcher.java b/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/OutgoingPacketPatcher.java index 86921e3..026fee3 100644 --- a/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/OutgoingPacketPatcher.java +++ b/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/OutgoingPacketPatcher.java @@ -6,7 +6,6 @@ import wasm.disassembly.modules.sections.code.Func; import wasm.disassembly.types.FuncType; import wasm.disassembly.types.ResultType; import wasm.disassembly.types.ValType; -import wasm.misc.CodeCompare; import wasm.misc.StreamReplacement; import java.util.Arrays; @@ -36,18 +35,16 @@ public class OutgoingPacketPatcher implements StreamReplacement { } @Override - public CodeCompare getCodeCompare() { - return code -> { - if (code.getLocalss().size() != 0) return false; - List expression = code.getExpression().getInstructions(); - if (expression.get(0).getInstrType() != InstrType.LOCAL_GET) return false; - if (expression.get(1).getInstrType() != InstrType.LOCAL_GET) return false; - if (expression.get(2).getInstrType() != InstrType.LOCAL_GET) return false; - if (expression.get(3).getInstrType() != InstrType.I32_LOAD) return false; - if (expression.get(4).getInstrType() != InstrType.I32_CONST) return false; - if (expression.get(5).getInstrType() != InstrType.CALL) return false; + public boolean codeMatches(Func code) { + if (code.getLocalss().size() != 0) return false; + List expression = code.getExpression().getInstructions(); + if (expression.get(0).getInstrType() != InstrType.LOCAL_GET) return false; + if (expression.get(1).getInstrType() != InstrType.LOCAL_GET) return false; + if (expression.get(2).getInstrType() != InstrType.LOCAL_GET) return false; + if (expression.get(3).getInstrType() != InstrType.I32_LOAD) return false; + if (expression.get(4).getInstrType() != InstrType.I32_CONST) return false; + if (expression.get(5).getInstrType() != InstrType.CALL) return false; - return true; - }; + return true; } } diff --git a/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/ReturnBytePatcher.java b/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/ReturnBytePatcher.java index 4d90c5c..6f8040c 100644 --- a/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/ReturnBytePatcher.java +++ b/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/ReturnBytePatcher.java @@ -6,7 +6,6 @@ import wasm.disassembly.modules.sections.code.Func; import wasm.disassembly.types.FuncType; import wasm.disassembly.types.ResultType; import wasm.disassembly.types.ValType; -import wasm.misc.CodeCompare; import wasm.misc.StreamReplacement; import java.util.Arrays; @@ -36,13 +35,11 @@ public class ReturnBytePatcher implements StreamReplacement { } @Override - public CodeCompare getCodeCompare() { - return code -> { - if (code.getLocalss().size() != 0) return false; - if (code.getExpression().getInstructions().size() != 30) return false; - List expr = code.getExpression().getInstructions(); - if (expr.get(expr.size() - 1).getInstrType() != InstrType.I32_XOR) return false; - return true; - }; + public boolean codeMatches(Func code) { + if (code.getLocalss().size() != 0) return false; + if (code.getExpression().getInstructions().size() != 30) return false; + List expr = code.getExpression().getInstructions(); + if (expr.get(expr.size() - 1).getInstrType() != InstrType.I32_XOR) return false; + return true; } } diff --git a/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/SetKeyPatcher.java b/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/SetKeyPatcher.java index f952105..41fab8a 100644 --- a/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/SetKeyPatcher.java +++ b/G-Earth/src/main/java/gearth/services/unity_tools/codepatcher/SetKeyPatcher.java @@ -7,7 +7,6 @@ import wasm.disassembly.modules.sections.code.Locals; import wasm.disassembly.types.FuncType; import wasm.disassembly.types.ResultType; import wasm.disassembly.types.ValType; -import wasm.misc.CodeCompare; import wasm.misc.StreamReplacement; import java.util.Arrays; @@ -38,23 +37,21 @@ public class SetKeyPatcher implements StreamReplacement { } @Override - public CodeCompare getCodeCompare() { - return code -> { - if (!(code.getLocalss().equals(Collections.singletonList(new Locals(1, ValType.I32))))) - return false; - List expectedExpr = Arrays.asList(InstrType.I32_CONST, InstrType.I32_LOAD8_S, - InstrType.I32_EQZ, InstrType.IF, InstrType.BLOCK, InstrType.LOCAL_GET, InstrType.I32_CONST, - InstrType.LOCAL_GET, InstrType.I32_LOAD, InstrType.I32_CONST, InstrType.I32_CONST, InstrType.I32_CONST, - InstrType.CALL); + public boolean codeMatches(Func code) { + if (!(code.getLocalss().equals(Collections.singletonList(new Locals(1, ValType.I32))))) + return false; + List expectedExpr = Arrays.asList(InstrType.I32_CONST, InstrType.I32_LOAD8_S, + InstrType.I32_EQZ, InstrType.IF, InstrType.BLOCK, InstrType.LOCAL_GET, InstrType.I32_CONST, + InstrType.LOCAL_GET, InstrType.I32_LOAD, InstrType.I32_CONST, InstrType.I32_CONST, InstrType.I32_CONST, + InstrType.CALL); - if (code.getExpression().getInstructions().size() != expectedExpr.size()) return false; + if (code.getExpression().getInstructions().size() != expectedExpr.size()) return false; - for (int j = 0; j < code.getExpression().getInstructions().size(); j++) { - Instr instr = code.getExpression().getInstructions().get(j); - if (instr.getInstrType() != expectedExpr.get(j)) return false; - } + for (int j = 0; j < code.getExpression().getInstructions().size(); j++) { + Instr instr = code.getExpression().getInstructions().get(j); + if (instr.getInstrType() != expectedExpr.get(j)) return false; + } - return true; - }; + return true; } } From dbf692cb3d6d30c1c89c76a5b3f25eff7f0efdd1 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 01:06:57 +0100 Subject: [PATCH 03/21] Added nitro button, reworked last selected client mode --- .../gearth/protocol/connection/HClient.java | 3 ++- .../ui/connection/ConnectionController.java | 27 +++++++++++++++---- .../gearth/ui/connection/Connection.fxml | 16 +++++++---- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/HClient.java b/G-Earth/src/main/java/gearth/protocol/connection/HClient.java index 54653c3..027fafd 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/HClient.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/HClient.java @@ -2,5 +2,6 @@ package gearth.protocol.connection; public enum HClient { UNITY, - FLASH + FLASH, + NITRO } diff --git a/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java b/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java index 287d70a..3a559da 100644 --- a/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java +++ b/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java @@ -2,6 +2,7 @@ package gearth.ui.connection; import gearth.Main; import gearth.misc.Cacher; +import gearth.protocol.connection.HClient; import gearth.protocol.connection.HState; import gearth.protocol.connection.proxy.ProxyProviderFactory; import gearth.services.Constants; @@ -38,10 +39,11 @@ public class ConnectionController extends SubForm { private volatile int fullyInitialized = 0; - public static final String USE_UNITY_CLIENT_CACHE_KEY = "use_unity"; + public static final String CLIENT_CACHE_KEY = "last_client_mode"; public ToggleGroup tgl_clientMode; public RadioButton rd_unity; public RadioButton rd_flash; + public RadioButton rd_nitro; public GridPane grd_clientSelection; private volatile int initcount = 0; @@ -54,9 +56,18 @@ public class ConnectionController extends SubForm { Constants.UNITY_PACKETS = rd_unity.isSelected(); }); - if (Cacher.getCacheContents().has(USE_UNITY_CLIENT_CACHE_KEY)) { - rd_unity.setSelected(Cacher.getCacheContents().getBoolean(USE_UNITY_CLIENT_CACHE_KEY)); - rd_flash.setSelected(!Cacher.getCacheContents().getBoolean(USE_UNITY_CLIENT_CACHE_KEY)); + if (Cacher.getCacheContents().has(CLIENT_CACHE_KEY)) { + switch (Cacher.getCacheContents().getEnum(HClient.class, CLIENT_CACHE_KEY)) { + case FLASH: + rd_flash.setSelected(true); + break; + case UNITY: + rd_unity.setSelected(true); + break; + case NITRO: + rd_nitro.setSelected(true); + break; + } } @@ -269,7 +280,13 @@ public class ConnectionController extends SubForm { @Override protected void onExit() { - Cacher.put(USE_UNITY_CLIENT_CACHE_KEY, rd_unity.isSelected()); + if (rd_flash.isSelected()) { + Cacher.put(CLIENT_CACHE_KEY, HClient.FLASH); + } else if (rd_unity.isSelected()) { + Cacher.put(CLIENT_CACHE_KEY, HClient.UNITY); + } else if (rd_nitro.isSelected()) { + Cacher.put(CLIENT_CACHE_KEY, HClient.NITRO); + } getHConnection().abort(); } diff --git a/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml b/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml index 96b87e5..bf7cb35 100644 --- a/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml +++ b/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml @@ -5,7 +5,7 @@ - + @@ -132,9 +132,9 @@ - - - + + + @@ -156,7 +156,13 @@ - + + + + + + + From 1c6d086bffcef64755330d053a346e2d576ba414 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 04:12:47 +0100 Subject: [PATCH 04/21] Add HTTP(S) MITM and replace websocket in config --- .gitignore | 4 + G-Earth/pom.xml | 6 +- .../java/gearth/protocol/HConnection.java | 7 + .../proxy/nitro/NitroProxyProvider.java | 53 ++++++++ .../proxy/nitro/http/NitroAuthority.java | 24 ++++ .../proxy/nitro/http/NitroHttpProxy.java | 89 +++++++++++++ .../nitro/http/NitroHttpProxyFilter.java | 124 ++++++++++++++++++ .../http/NitroHttpProxyFilterSource.java | 46 +++++++ .../http/NitroHttpProxyServerCallback.java | 14 ++ .../proxy/nitro/os/NitroOsFunctions.java | 13 ++ .../nitro/os/NitroOsFunctionsFactory.java | 21 +++ .../proxy/nitro/os/windows/NitroWindows.java | 42 ++++++ .../ui/connection/ConnectionController.java | 27 +++- 13 files changed, 464 insertions(+), 6 deletions(-) create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroAuthority.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyServerCallback.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctionsFactory.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java diff --git a/.gitignore b/.gitignore index 902afbc..beed7d5 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,7 @@ Extensions/BlockReplacePackets/.classpath *.settings/org.eclipse.jdt.apt.core.prefs *.settings/org.eclipse.jdt.core.prefs *.settings/org.eclipse.m2e.core.prefs + +# Certificates +*.p12 +*.pem \ No newline at end of file diff --git a/G-Earth/pom.xml b/G-Earth/pom.xml index 09b1b80..9164616 100644 --- a/G-Earth/pom.xml +++ b/G-Earth/pom.xml @@ -224,7 +224,11 @@ bytes 1.5.0 - + + com.github.ganskef + littleproxy-mitm + 1.1.0 + diff --git a/G-Earth/src/main/java/gearth/protocol/HConnection.java b/G-Earth/src/main/java/gearth/protocol/HConnection.java index db1c083..70bd0c4 100644 --- a/G-Earth/src/main/java/gearth/protocol/HConnection.java +++ b/G-Earth/src/main/java/gearth/protocol/HConnection.java @@ -1,6 +1,7 @@ package gearth.protocol; import gearth.misc.listenerpattern.Observable; +import gearth.protocol.connection.proxy.nitro.NitroProxyProvider; import gearth.services.packet_info.PacketInfoManager; import gearth.protocol.connection.HClient; import gearth.protocol.connection.HProxy; @@ -68,6 +69,12 @@ public class HConnection { startMITM(); } + public void startNitro() { + HConnection selff = this; + proxyProvider = new NitroProxyProvider(proxy -> selff.proxy = proxy, selff::setState, this); + startMITM(); + } + private void startMITM() { try { if (proxyProvider != null) { diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java new file mode 100644 index 0000000..5ba1172 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java @@ -0,0 +1,53 @@ +package gearth.protocol.connection.proxy.nitro; + +import gearth.protocol.HConnection; +import gearth.protocol.connection.HProxySetter; +import gearth.protocol.connection.HState; +import gearth.protocol.connection.HStateSetter; +import gearth.protocol.connection.proxy.ProxyProvider; +import gearth.protocol.connection.proxy.nitro.http.NitroHttpProxy; + +import java.io.IOException; + +public class NitroProxyProvider implements ProxyProvider { + + private final HProxySetter proxySetter; + private final HStateSetter stateSetter; + private final HConnection hConnection; + private final NitroHttpProxy nitroProxy; + + public NitroProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection hConnection) { + this.proxySetter = proxySetter; + this.stateSetter = stateSetter; + this.hConnection = hConnection; + this.nitroProxy = new NitroHttpProxy(); + } + + @Override + public void start() throws IOException { + if (!nitroProxy.start()) { + System.out.println("Failed to start nitro proxy"); + + stateSetter.setState(HState.NOT_CONNECTED); + return; + } + + stateSetter.setState(HState.WAITING_FOR_CLIENT); + } + + @Override + public void abort() { + stateSetter.setState(HState.ABORTING); + + new Thread(() -> { + try { + nitroProxy.stop(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + stateSetter.setState(HState.NOT_CONNECTED); + } + }).start(); + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroAuthority.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroAuthority.java new file mode 100644 index 0000000..fd5bd4e --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroAuthority.java @@ -0,0 +1,24 @@ +package gearth.protocol.connection.proxy.nitro.http; + +import org.littleshoot.proxy.mitm.Authority; + +import java.io.File; + +public class NitroAuthority extends Authority { + + private static final String CERT_ALIAS = "gearth-nitro"; + private static final String CERT_ORGANIZATION = "G-Earth Nitro"; + private static final String CERT_DESCRIPTION = "G-Earth nitro support"; + + public NitroAuthority() { + super(new File("."), + CERT_ALIAS, + "verysecure".toCharArray(), + CERT_DESCRIPTION, + CERT_ORGANIZATION, + "Certificate Authority", + CERT_ORGANIZATION, + CERT_DESCRIPTION); + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java new file mode 100644 index 0000000..13acc03 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java @@ -0,0 +1,89 @@ +package gearth.protocol.connection.proxy.nitro.http; + +import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions; +import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctionsFactory; +import org.littleshoot.proxy.HttpProxyServer; +import org.littleshoot.proxy.impl.DefaultHttpProxyServer; +import org.littleshoot.proxy.mitm.Authority; +import org.littleshoot.proxy.mitm.CertificateSniffingMitmManager; +import org.littleshoot.proxy.mitm.RootCertificateException; + +public class NitroHttpProxy { + + private final Authority authority; + private final NitroOsFunctions osFunctions; + + private HttpProxyServer proxyServer = null; + + public NitroHttpProxy() { + this.authority = new NitroAuthority(); + this.osFunctions = NitroOsFunctionsFactory.create(); + } + + private boolean initializeCertificate() { + return this.osFunctions.installRootCertificate(this.authority.aliasFile(".pem")); + } + + /** + * Register HTTP(s) proxy on the system. + */ + private boolean registerProxy() { + return this.osFunctions.registerSystemProxy("127.0.0.1", 9090); + } + + /** + * Unregister HTTP(s) proxy from system. + */ + private boolean unregisterProxy() { + return this.osFunctions.unregisterSystemProxy(); + } + + public boolean start() { + + + try { + proxyServer = DefaultHttpProxyServer.bootstrap() + .withPort(9090) + .withManInTheMiddle(new CertificateSniffingMitmManager(authority)) + // TODO: Replace lambda with some class + .withFiltersSource(new NitroHttpProxyFilterSource((configUrl, websocketUrl) -> { + System.out.printf("Found %s at %s%n", websocketUrl, configUrl); + + return "wss://127.0.0.1:2096"; + })) + .start(); + + if (!initializeCertificate()) { + proxyServer.stop(); + + System.out.println("Failed to initialize certificate"); + return false; + } + + if (!registerProxy()) { + proxyServer.stop(); + + System.out.println("Failed to register certificate"); + return false; + } + + return true; + } catch (RootCertificateException e) { + e.printStackTrace(); + return false; + } + } + + public void stop() { + if (!unregisterProxy()) { + System.out.println("Failed to unregister system proxy, please check manually"); + } + + if (proxyServer == null) { + return; + } + + proxyServer.stop(); + proxyServer = null; + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java new file mode 100644 index 0000000..28a33ae --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java @@ -0,0 +1,124 @@ +package gearth.protocol.connection.proxy.nitro.http; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.*; +import io.netty.util.CharsetUtil; +import org.littleshoot.proxy.HttpFiltersAdapter; + +import java.nio.charset.StandardCharsets; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class NitroHttpProxyFilter extends HttpFiltersAdapter { + + private static final String NitroConfigSearch = "\"socket.url\""; + private static final Pattern NitroConfigPattern = Pattern.compile("\"socket\\.url\":.?\"(wss?://.*?)\"", Pattern.MULTILINE); + + private static final String HeaderAcceptEncoding = "Accept-Encoding"; + private static final String HeaderAge = "Age"; + private static final String HeaderCacheControl = "Cache-Control"; + private static final String HeaderETag = "ETag"; + private static final String HeaderIfNoneMatch = "If-None-Match"; + private static final String HeaderIfModifiedSince = "If-Modified-Since"; + private static final String HeaderLastModified = "Last-Modified"; + + private final NitroHttpProxyServerCallback callback; + private final String url; + + public NitroHttpProxyFilter(HttpRequest originalRequest, ChannelHandlerContext ctx, NitroHttpProxyServerCallback callback, String url) { + super(originalRequest, ctx); + this.callback = callback; + this.url = url; + } + + @Override + public HttpResponse clientToProxyRequest(HttpObject httpObject) { + if (httpObject instanceof HttpRequest) { + HttpRequest request = (HttpRequest) httpObject; + HttpHeaders headers = request.headers(); + + // Only support gzip or deflate. + // The LittleProxy library does not support brotli. + if (headers.contains(HeaderAcceptEncoding)) { + String encoding = headers.get(HeaderAcceptEncoding); + + if (encoding.contains("br")) { + if (encoding.contains("gzip") && encoding.contains("deflate")) { + headers.set(HeaderAcceptEncoding, "gzip, deflate"); + } else if (encoding.contains("gzip")) { + headers.set(HeaderAcceptEncoding, "gzip, deflate"); + } else { + headers.remove(HeaderAcceptEncoding); + } + } + } + + // Disable caching. + stripCacheHeaders(headers); + } + + return super.clientToProxyRequest(httpObject); + } + + @Override + public HttpObject serverToProxyResponse(HttpObject httpObject) { + if (httpObject instanceof FullHttpResponse) { + final FullHttpResponse response = (FullHttpResponse) httpObject; + + // Find nitro configuration file. + boolean responseModified = false; + String responseBody = responseRead(response); + + if (responseBody.contains(NitroConfigSearch)) { + final Matcher matcher = NitroConfigPattern.matcher(responseBody); + + if (matcher.find()) { + final String originalWebsocket = matcher.group(1); + final String replacementWebsocket = callback.replaceWebsocketServer(url, originalWebsocket); + + if (replacementWebsocket != null) { + responseBody = responseBody.replace(originalWebsocket, replacementWebsocket); + responseModified = true; + } + } + } + + // Apply changes. + if (responseModified) { + responseWrite(response, responseBody); + } + } + + return httpObject; + } + + private static String responseRead(FullHttpResponse response) { + final ByteBuf contentBuf = response.content(); + return contentBuf.toString(CharsetUtil.UTF_8); + } + + private static void responseWrite(FullHttpResponse response, String content) { + final byte[] body = content.getBytes(StandardCharsets.UTF_8); + + // Update content. + response.content().clear().writeBytes(body); + + // Update content-length. + HttpHeaders.setContentLength(response, body.length); + + // Ensure modified response is not cached. + stripCacheHeaders(response.headers()); + } + + private static void stripCacheHeaders(HttpHeaders headers) { + headers.remove(HeaderAcceptEncoding); + headers.remove(HeaderAge); + headers.remove(HeaderCacheControl); + headers.remove(HeaderETag); + headers.remove(HeaderIfNoneMatch); + headers.remove(HeaderIfModifiedSince); + headers.remove(HeaderLastModified); + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java new file mode 100644 index 0000000..fae9deb --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java @@ -0,0 +1,46 @@ +package gearth.protocol.connection.proxy.nitro.http; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.util.AttributeKey; +import org.littleshoot.proxy.HttpFilters; +import org.littleshoot.proxy.HttpFiltersAdapter; +import org.littleshoot.proxy.HttpFiltersSourceAdapter; + +public class NitroHttpProxyFilterSource extends HttpFiltersSourceAdapter { + + private static final AttributeKey CONNECTED_URL = AttributeKey.valueOf("connected_url"); + + private final NitroHttpProxyServerCallback callback; + + public NitroHttpProxyFilterSource(NitroHttpProxyServerCallback callback) { + this.callback = callback; + } + + @Override + public HttpFilters filterRequest(HttpRequest originalRequest, ChannelHandlerContext ctx) { + // https://github.com/ganskef/LittleProxy-mitm#resolving-uri-in-case-of-https + String uri = originalRequest.getUri(); + if (originalRequest.getMethod() == HttpMethod.CONNECT) { + if (ctx != null) { + String prefix = "https://" + uri.replaceFirst(":443$", ""); + ctx.channel().attr(CONNECTED_URL).set(prefix); + } + return new HttpFiltersAdapter(originalRequest, ctx); + } + + String connectedUrl = ctx.channel().attr(CONNECTED_URL).get(); + if (connectedUrl == null) { + return new NitroHttpProxyFilter(originalRequest, ctx, callback, uri); + } + + return new NitroHttpProxyFilter(originalRequest, ctx, callback, connectedUrl + uri); + } + + @Override + public int getMaximumResponseBufferSizeInBytes() { + // Increasing this causes LittleProxy to output "FullHttpResponse" objects. + return 1024 * 1024 * 1024; + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyServerCallback.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyServerCallback.java new file mode 100644 index 0000000..3f04f11 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyServerCallback.java @@ -0,0 +1,14 @@ +package gearth.protocol.connection.proxy.nitro.http; + +public interface NitroHttpProxyServerCallback { + + /** + * Specify a replacement for the given websocket url. + * + * @param configUrl The url at which the websocket url was found. + * @param websocketUrl The hotel websocket url. + * @return Return null to not replace anything, otherwise specify an alternative websocket url. + */ + String replaceWebsocketServer(String configUrl, String websocketUrl); + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java new file mode 100644 index 0000000..d77a4c6 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java @@ -0,0 +1,13 @@ +package gearth.protocol.connection.proxy.nitro.os; + +import java.io.File; + +public interface NitroOsFunctions { + + boolean installRootCertificate(File certificate); + + boolean registerSystemProxy(String host, int port); + + boolean unregisterSystemProxy(); + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctionsFactory.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctionsFactory.java new file mode 100644 index 0000000..18bbf7c --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctionsFactory.java @@ -0,0 +1,21 @@ +package gearth.protocol.connection.proxy.nitro.os; + +import gearth.misc.OSValidator; +import gearth.protocol.connection.proxy.nitro.os.windows.NitroWindows; +import org.apache.commons.lang3.NotImplementedException; + +public final class NitroOsFunctionsFactory { + + public static NitroOsFunctions create() { + if (OSValidator.isWindows()) { + return new NitroWindows(); + } + + if (OSValidator.isUnix()) { + throw new NotImplementedException("unix nitro is not implemented yet"); + } + + throw new NotImplementedException("macOS nitro is not implemented yet"); + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java new file mode 100644 index 0000000..27c7b12 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java @@ -0,0 +1,42 @@ +package gearth.protocol.connection.proxy.nitro.os.windows; + +import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions; + +import java.io.File; + +public class NitroWindows implements NitroOsFunctions { + + @Override + public boolean installRootCertificate(File certificate) { + // TODO: Prompt registration + System.out.println(certificate.toString()); + return true; + } + + @Override + public boolean registerSystemProxy(String host, int port) { + try { + final String proxy = String.format("%s:%d", host, port); + Runtime.getRuntime().exec("reg add \"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v ProxyServer /t REG_SZ /d \"" + proxy + "\" /f"); + Runtime.getRuntime().exec("reg add \"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v ProxyEnable /t REG_DWORD /d 1 /f"); + return true; + } catch (Exception e) { + e.printStackTrace(); + } + + return false; + } + + @Override + public boolean unregisterSystemProxy() { + try { + Runtime.getRuntime().exec("reg add \"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v ProxyEnable /t REG_DWORD /d 0 /f"); + return true; + } catch (Exception e) { + e.printStackTrace(); + } + + return false; + } + +} diff --git a/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java b/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java index 3a559da..b908666 100644 --- a/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java +++ b/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java @@ -246,6 +246,10 @@ public class ConnectionController extends SubForm { Platform.runLater(() -> rd_unity.setSelected(true)); getHConnection().startUnity(); } + else if (connectMode.equals("nitro")) { + Platform.runLater(() -> rd_nitro.setSelected(true)); + getHConnection().startNitro(); + } Platform.runLater(this::updateInputUI); } } @@ -255,16 +259,16 @@ public class ConnectionController extends SubForm { btnConnect.setDisable(true); new Thread(() -> { - if (useFlash()) { + if (isClientMode(HClient.FLASH)) { if (cbx_autodetect.isSelected()) { getHConnection().start(); - } - else { + } else { getHConnection().start(inpHost.getEditor().getText(), Integer.parseInt(inpPort.getEditor().getText())); } - } - else { + } else if (isClientMode(HClient.UNITY)) { getHConnection().startUnity(); + } else if (isClientMode(HClient.NITRO)) { + getHConnection().startNitro(); } @@ -297,4 +301,17 @@ public class ConnectionController extends SubForm { private boolean useFlash() { return rd_flash.isSelected(); } + + private boolean isClientMode(HClient client) { + switch (client) { + case FLASH: + return rd_flash.isSelected(); + case UNITY: + return rd_unity.isSelected(); + case NITRO: + return rd_nitro.isSelected(); + } + + return false; + } } From 368a21ffcf525487df7f372ff837521e88b9b3ba Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 06:19:07 +0100 Subject: [PATCH 05/21] Added websocket proxying for nitro with broken outgoing packets --- .../proxy/nitro/NitroConstants.java | 11 +++ .../proxy/nitro/NitroProxyProvider.java | 64 ++++++++++++--- .../proxy/nitro/http/NitroHttpProxy.java | 24 +++--- .../proxy/nitro/websocket/NitroForwarder.java | 17 ++++ .../nitro/websocket/NitroPacketHandler.java | 56 +++++++++++++ .../proxy/nitro/websocket/NitroSession.java | 9 +++ .../nitro/websocket/NitroWebsocketClient.java | 78 +++++++++++++++++++ .../nitro/websocket/NitroWebsocketProxy.java | 66 ++++++++++++++++ .../nitro/websocket/NitroWebsocketServer.java | 65 ++++++++++++++++ .../NitroWebsocketServerConfigurator.java | 28 +++++++ .../packet_info/PacketInfoManager.java | 3 +- 11 files changed, 395 insertions(+), 26 deletions(-) create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroForwarder.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroSession.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServerConfigurator.java diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java new file mode 100644 index 0000000..d64a88f --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java @@ -0,0 +1,11 @@ +package gearth.protocol.connection.proxy.nitro; + +public final class NitroConstants { + + public static final int PORT_WEBSOCKET = 2096; + public static final int PORT_HTTP = 9090; + + public static final int WEBSOCKET_BUFFER_SIZE = 1024 * 1024 * 10; + public static final String WEBSOCKET_REVISION = "PRODUCTION-201611291003-338511768"; + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java index 5ba1172..a3ac2ec 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java @@ -1,34 +1,52 @@ package gearth.protocol.connection.proxy.nitro; import gearth.protocol.HConnection; +import gearth.protocol.StateChangeListener; import gearth.protocol.connection.HProxySetter; import gearth.protocol.connection.HState; import gearth.protocol.connection.HStateSetter; import gearth.protocol.connection.proxy.ProxyProvider; import gearth.protocol.connection.proxy.nitro.http.NitroHttpProxy; +import gearth.protocol.connection.proxy.nitro.http.NitroHttpProxyServerCallback; +import gearth.protocol.connection.proxy.nitro.websocket.NitroWebsocketProxy; import java.io.IOException; -public class NitroProxyProvider implements ProxyProvider { +public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCallback, StateChangeListener { private final HProxySetter proxySetter; private final HStateSetter stateSetter; - private final HConnection hConnection; - private final NitroHttpProxy nitroProxy; + private final HConnection connection; + private final NitroHttpProxy nitroHttpProxy; + private final NitroWebsocketProxy nitroWebsocketProxy; - public NitroProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection hConnection) { + private String originalWebsocketUrl; + + public NitroProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection) { this.proxySetter = proxySetter; this.stateSetter = stateSetter; - this.hConnection = hConnection; - this.nitroProxy = new NitroHttpProxy(); + this.connection = connection; + this.nitroHttpProxy = new NitroHttpProxy(this); + this.nitroWebsocketProxy = new NitroWebsocketProxy(proxySetter, stateSetter, connection, this); + } + + public String getOriginalWebsocketUrl() { + return originalWebsocketUrl; } @Override public void start() throws IOException { - if (!nitroProxy.start()) { - System.out.println("Failed to start nitro proxy"); + connection.getStateObservable().addListener(this); - stateSetter.setState(HState.NOT_CONNECTED); + if (!nitroHttpProxy.start()) { + System.out.println("Failed to start nitro proxy"); + abort(); + return; + } + + if (!nitroWebsocketProxy.start()) { + System.out.println("Failed to start nitro websocket proxy"); + abort(); return; } @@ -41,13 +59,35 @@ public class NitroProxyProvider implements ProxyProvider { new Thread(() -> { try { - nitroProxy.stop(); + nitroHttpProxy.stop(); } catch (Exception e) { e.printStackTrace(); - } finally { - stateSetter.setState(HState.NOT_CONNECTED); } + + try { + nitroWebsocketProxy.stop(); + } catch (Exception e) { + e.printStackTrace(); + } + + stateSetter.setState(HState.NOT_CONNECTED); + + connection.getStateObservable().removeListener(this); }).start(); } + @Override + public String replaceWebsocketServer(String configUrl, String websocketUrl) { + originalWebsocketUrl = websocketUrl; + return String.format("ws://127.0.0.1:%d", NitroConstants.PORT_WEBSOCKET); + } + + @Override + public void stateChanged(HState oldState, HState newState) { + if (oldState == HState.WAITING_FOR_CLIENT && newState == HState.CONNECTED) { + // Unregister but do not stop http proxy. + // We are not stopping the http proxy because some requests might still require it to be running. + nitroHttpProxy.pause(); + } + } } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java index 13acc03..7e23a75 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java @@ -1,5 +1,6 @@ package gearth.protocol.connection.proxy.nitro.http; +import gearth.protocol.connection.proxy.nitro.NitroConstants; import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions; import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctionsFactory; import org.littleshoot.proxy.HttpProxyServer; @@ -12,10 +13,12 @@ public class NitroHttpProxy { private final Authority authority; private final NitroOsFunctions osFunctions; + private final NitroHttpProxyServerCallback serverCallback; private HttpProxyServer proxyServer = null; - public NitroHttpProxy() { + public NitroHttpProxy(NitroHttpProxyServerCallback serverCallback) { + this.serverCallback = serverCallback; this.authority = new NitroAuthority(); this.osFunctions = NitroOsFunctionsFactory.create(); } @@ -28,7 +31,7 @@ public class NitroHttpProxy { * Register HTTP(s) proxy on the system. */ private boolean registerProxy() { - return this.osFunctions.registerSystemProxy("127.0.0.1", 9090); + return this.osFunctions.registerSystemProxy("127.0.0.1", NitroConstants.PORT_HTTP); } /** @@ -39,18 +42,11 @@ public class NitroHttpProxy { } public boolean start() { - - try { proxyServer = DefaultHttpProxyServer.bootstrap() - .withPort(9090) + .withPort(NitroConstants.PORT_HTTP) .withManInTheMiddle(new CertificateSniffingMitmManager(authority)) - // TODO: Replace lambda with some class - .withFiltersSource(new NitroHttpProxyFilterSource((configUrl, websocketUrl) -> { - System.out.printf("Found %s at %s%n", websocketUrl, configUrl); - - return "wss://127.0.0.1:2096"; - })) + .withFiltersSource(new NitroHttpProxyFilterSource(serverCallback)) .start(); if (!initializeCertificate()) { @@ -74,10 +70,14 @@ public class NitroHttpProxy { } } - public void stop() { + public void pause() { if (!unregisterProxy()) { System.out.println("Failed to unregister system proxy, please check manually"); } + } + + public void stop() { + pause(); if (proxyServer == null) { return; diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroForwarder.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroForwarder.java new file mode 100644 index 0000000..42433da --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroForwarder.java @@ -0,0 +1,17 @@ +package gearth.protocol.connection.proxy.nitro.websocket; + +import gearth.protocol.HConnection; + +public class NitroForwarder { + + private final HConnection connection; + private final NitroSession client; + private final NitroSession server; + + public NitroForwarder(HConnection connection, NitroSession client, NitroSession server) { + this.connection = connection; + this.client = client; + this.server = server; + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java new file mode 100644 index 0000000..a6283e3 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java @@ -0,0 +1,56 @@ +package gearth.protocol.connection.proxy.nitro.websocket; + +import gearth.protocol.HMessage; +import gearth.protocol.HPacket; +import gearth.protocol.packethandler.PacketHandler; +import gearth.services.extension_handler.ExtensionHandler; +import gearth.services.extension_handler.OnHMessageHandled; + +import java.io.IOException; +import java.nio.ByteBuffer; + +public class NitroPacketHandler extends PacketHandler { + + private final HMessage.Direction direction; + private final NitroSession session; + + protected NitroPacketHandler(HMessage.Direction direction, NitroSession session, ExtensionHandler extensionHandler, Object[] trafficObservables) { + super(extensionHandler, trafficObservables); + this.direction = direction; + this.session = session; + } + + @Override + public boolean sendToStream(byte[] buffer) { + session.getSession().getAsyncRemote().sendBinary(ByteBuffer.wrap(buffer)); + return true; + } + + @Override + public void act(byte[] buffer) throws IOException { + HMessage hMessage = new HMessage(new HPacket(deepCopy(buffer)), direction, currentIndex); + + OnHMessageHandled afterExtensionIntercept = hMessage1 -> { + notifyListeners(2, hMessage1); + + if (!hMessage1.isBlocked()) { + sendToStream(hMessage1.getPacket().toBytes()); + } + }; + + notifyListeners(0, hMessage); + notifyListeners(1, hMessage); + extensionHandler.handle(hMessage, afterExtensionIntercept); + + currentIndex++; + } + + public static byte[] deepCopy(byte[] org) { + if (org == null) + return null; + byte[] result = new byte[org.length]; + System.arraycopy(org, 0, result, 0, org.length); + return result; + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroSession.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroSession.java new file mode 100644 index 0000000..f85854e --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroSession.java @@ -0,0 +1,9 @@ +package gearth.protocol.connection.proxy.nitro.websocket; + +import javax.websocket.Session; + +public interface NitroSession { + + Session getSession(); + +} 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 new file mode 100644 index 0000000..d1c8fa5 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java @@ -0,0 +1,78 @@ +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.NitroConstants; +import gearth.protocol.connection.proxy.nitro.NitroProxyProvider; +import gearth.services.internal_extensions.uilogger.hexdumper.Hexdump; + +import javax.websocket.*; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; + +@ServerEndpoint(value = "/") +public class NitroWebsocketClient implements NitroSession { + + private final HProxySetter proxySetter; + private final HStateSetter stateSetter; + private final HConnection connection; + private final NitroProxyProvider proxyProvider; + private final NitroWebsocketServer server; + private final NitroPacketHandler packetHandler; + + private Session activeSession = null; + private HProxy proxy = null; + + public NitroWebsocketClient(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection, NitroProxyProvider proxyProvider) { + this.proxySetter = proxySetter; + this.stateSetter = stateSetter; + this.connection = connection; + this.proxyProvider = proxyProvider; + this.server = new NitroWebsocketServer(connection, this); + this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOSERVER, server, connection.getExtensionHandler(), connection.getTrafficObservables()); + } + + @OnOpen + public void onOpen(Session session) throws IOException { + activeSession = session; + activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE); + + server.connect(proxyProvider.getOriginalWebsocketUrl()); + + proxy = new HProxy(HClient.NITRO, "", "", -1, -1, ""); + proxy.verifyProxy( + this.server.getPacketHandler(), + this.packetHandler, + NitroConstants.WEBSOCKET_REVISION, + "HTML5" // TODO: What is its purpose? + ); + + proxySetter.setProxy(proxy); + stateSetter.setState(HState.CONNECTED); + } + + @OnMessage + public void onMessage(byte[] b, Session session) throws IOException { + System.out.printf("onMessage (%d)%n", b.length); + System.out.println(session); + System.out.println(Hexdump.hexdump(b)); + + packetHandler.act(b); + } + + @OnClose + public void onClose(Session session) throws IOException { + activeSession = null; + } + + @OnError + public void onError(Session session, Throwable throwable) { + + } + + @Override + public Session getSession() { + return activeSession; + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java new file mode 100644 index 0000000..d162347 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java @@ -0,0 +1,66 @@ +package gearth.protocol.connection.proxy.nitro.websocket; + +import gearth.protocol.HConnection; +import gearth.protocol.connection.HProxySetter; +import gearth.protocol.connection.HStateSetter; +import gearth.protocol.connection.proxy.nitro.NitroConstants; +import gearth.protocol.connection.proxy.nitro.NitroProxyProvider; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.HandlerList; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; + +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerEndpointConfig; + +public class NitroWebsocketProxy { + + private final HProxySetter proxySetter; + private final HStateSetter stateSetter; + private final HConnection connection; + private final NitroProxyProvider proxyProvider; + + private final Server server; + + public NitroWebsocketProxy(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection, NitroProxyProvider proxyProvider) { + this.proxySetter = proxySetter; + this.stateSetter = stateSetter; + this.connection = connection; + this.proxyProvider = proxyProvider; + this.server = new Server(NitroConstants.PORT_WEBSOCKET); + } + + public boolean start() { + try { + final ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + + final HandlerList handlers = new HandlerList(); + handlers.setHandlers(new Handler[] { context }); + + final ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context); + wscontainer.addEndpoint(ServerEndpointConfig.Builder + .create(NitroWebsocketClient.class, "/") + .configurator(new NitroWebsocketServerConfigurator(proxySetter, stateSetter, connection, proxyProvider)) + .build()); + + server.setHandler(handlers); + server.start(); + + return true; + } catch (Exception e) { + e.printStackTrace(); + } + + return false; + } + + public void stop() { + try { + server.stop(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} 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 new file mode 100644 index 0000000..f7928df --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java @@ -0,0 +1,65 @@ +package gearth.protocol.connection.proxy.nitro.websocket; + +import gearth.protocol.HConnection; +import gearth.protocol.HMessage; +import gearth.protocol.connection.proxy.nitro.NitroConstants; +import gearth.protocol.packethandler.PacketHandler; + +import javax.websocket.*; +import java.io.IOException; +import java.net.URI; + +@ClientEndpoint +public class NitroWebsocketServer implements NitroSession { + + private final PacketHandler packetHandler; + private Session activeSession = null; + + public NitroWebsocketServer(HConnection connection, NitroWebsocketClient client) { + this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOCLIENT, client, connection.getExtensionHandler(), connection.getTrafficObservables()); + } + + public void connect(String websocketUrl) throws IOException { + try { + ContainerProvider.getWebSocketContainer().connectToServer(this, URI.create(websocketUrl)); + } catch (DeploymentException e) { + throw new IOException("Failed to deploy websocket client", e); + } + } + + @OnOpen + public void onOpen(Session Session) { + this.activeSession = Session; + this.activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE); + } + + @OnMessage + public void onMessage(byte[] b, Session session) throws IOException { + //System.out.printf("onMessage (%d)%n", b.length); + //System.out.println(session); + //System.out.println(Hexdump.hexdump(b)); + + packetHandler.act(b); + } + + @OnClose + public void onClose(Session userSession, CloseReason reason) { + //System.out.println("closing websocket"); + } + + @OnError + public void onError(Session session, Throwable throwable) { + //System.out.println("onError"); + //System.out.println(session); + //System.out.println(throwable); + } + + @Override + public Session getSession() { + return activeSession; + } + + public PacketHandler getPacketHandler() { + return packetHandler; + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServerConfigurator.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServerConfigurator.java new file mode 100644 index 0000000..8155f27 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServerConfigurator.java @@ -0,0 +1,28 @@ +package gearth.protocol.connection.proxy.nitro.websocket; + +import gearth.protocol.HConnection; +import gearth.protocol.connection.HProxySetter; +import gearth.protocol.connection.HStateSetter; +import gearth.protocol.connection.proxy.nitro.NitroProxyProvider; + +import javax.websocket.server.ServerEndpointConfig; + +public class NitroWebsocketServerConfigurator extends ServerEndpointConfig.Configurator { + + private final HProxySetter proxySetter; + private final HStateSetter stateSetter; + private final HConnection connection; + private final NitroProxyProvider proxyProvider; + + public NitroWebsocketServerConfigurator(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection, NitroProxyProvider proxyProvider) { + this.proxySetter = proxySetter; + this.stateSetter = stateSetter; + this.connection = connection; + this.proxyProvider = proxyProvider; + } + + @Override + public T getEndpointInstance(Class endpointClass) { + return (T) new NitroWebsocketClient(proxySetter, stateSetter, connection, proxyProvider); + } +} diff --git a/G-Earth/src/main/java/gearth/services/packet_info/PacketInfoManager.java b/G-Earth/src/main/java/gearth/services/packet_info/PacketInfoManager.java index 27904b4..af1fda8 100644 --- a/G-Earth/src/main/java/gearth/services/packet_info/PacketInfoManager.java +++ b/G-Earth/src/main/java/gearth/services/packet_info/PacketInfoManager.java @@ -114,8 +114,7 @@ public class PacketInfoManager { if (clientType == HClient.UNITY) { result.addAll(new GEarthUnityPacketInfoProvider(hotelversion).provide()); - } - else if (clientType == HClient.FLASH) { + } else if (clientType == HClient.FLASH || clientType == HClient.NITRO) { try { List providers = new ArrayList<>(); providers.add(new HarblePacketInfoProvider(hotelversion)); From 7a635ffe6a3c6c597eac8e4786b649d0b86b8d1d Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 06:48:33 +0100 Subject: [PATCH 06/21] Fix outgoing packet --- .../nitro/websocket/NitroPacketHandler.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java index a6283e3..8ea0ee8 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java @@ -22,13 +22,18 @@ public class NitroPacketHandler extends PacketHandler { @Override public boolean sendToStream(byte[] buffer) { + // Required to prevent garbage buffer within the UI logger. + if (direction == HMessage.Direction.TOSERVER) { + buffer = buffer.clone(); + } + session.getSession().getAsyncRemote().sendBinary(ByteBuffer.wrap(buffer)); return true; } @Override public void act(byte[] buffer) throws IOException { - HMessage hMessage = new HMessage(new HPacket(deepCopy(buffer)), direction, currentIndex); + HMessage hMessage = new HMessage(new HPacket(buffer), direction, currentIndex); OnHMessageHandled afterExtensionIntercept = hMessage1 -> { notifyListeners(2, hMessage1); @@ -44,13 +49,4 @@ public class NitroPacketHandler extends PacketHandler { currentIndex++; } - - public static byte[] deepCopy(byte[] org) { - if (org == null) - return null; - byte[] result = new byte[org.length]; - System.arraycopy(org, 0, result, 0, org.length); - return result; - } - } From dd0312c020d299bb56740a475526f8801299cf3f Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 18:33:38 +0100 Subject: [PATCH 07/21] Improve nitro constants --- .../protocol/connection/proxy/nitro/NitroConstants.java | 6 ++++-- .../protocol/connection/proxy/nitro/NitroProxyProvider.java | 2 +- .../connection/proxy/nitro/http/NitroHttpProxy.java | 4 ++-- .../proxy/nitro/http/NitroHttpProxyFilterSource.java | 3 ++- .../proxy/nitro/websocket/NitroWebsocketClient.java | 3 +-- .../proxy/nitro/websocket/NitroWebsocketProxy.java | 2 +- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java index d64a88f..2d111f3 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java @@ -2,10 +2,12 @@ package gearth.protocol.connection.proxy.nitro; public final class NitroConstants { - public static final int PORT_WEBSOCKET = 2096; - public static final int PORT_HTTP = 9090; + public static final int HTTP_PORT = 9090; + public static final int HTTP_BUFFER_SIZE = 1024 * 1024 * 10; + public static final int WEBSOCKET_PORT = 2096; public static final int WEBSOCKET_BUFFER_SIZE = 1024 * 1024 * 10; public static final String WEBSOCKET_REVISION = "PRODUCTION-201611291003-338511768"; + public static final String WEBSOCKET_CLIENT_IDENTIFIER = "HTML5"; } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java index a3ac2ec..3cb47a1 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java @@ -79,7 +79,7 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa @Override public String replaceWebsocketServer(String configUrl, String websocketUrl) { originalWebsocketUrl = websocketUrl; - return String.format("ws://127.0.0.1:%d", NitroConstants.PORT_WEBSOCKET); + return String.format("ws://127.0.0.1:%d", NitroConstants.WEBSOCKET_PORT); } @Override diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java index 7e23a75..84885ee 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java @@ -31,7 +31,7 @@ public class NitroHttpProxy { * Register HTTP(s) proxy on the system. */ private boolean registerProxy() { - return this.osFunctions.registerSystemProxy("127.0.0.1", NitroConstants.PORT_HTTP); + return this.osFunctions.registerSystemProxy("127.0.0.1", NitroConstants.HTTP_PORT); } /** @@ -44,7 +44,7 @@ public class NitroHttpProxy { public boolean start() { try { proxyServer = DefaultHttpProxyServer.bootstrap() - .withPort(NitroConstants.PORT_HTTP) + .withPort(NitroConstants.HTTP_PORT) .withManInTheMiddle(new CertificateSniffingMitmManager(authority)) .withFiltersSource(new NitroHttpProxyFilterSource(serverCallback)) .start(); diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java index fae9deb..c736458 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java @@ -1,5 +1,6 @@ package gearth.protocol.connection.proxy.nitro.http; +import gearth.protocol.connection.proxy.nitro.NitroConstants; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequest; @@ -41,6 +42,6 @@ public class NitroHttpProxyFilterSource extends HttpFiltersSourceAdapter { @Override public int getMaximumResponseBufferSizeInBytes() { // Increasing this causes LittleProxy to output "FullHttpResponse" objects. - return 1024 * 1024 * 1024; + return NitroConstants.HTTP_BUFFER_SIZE; } } 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 d1c8fa5..ab890da 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 @@ -5,7 +5,6 @@ import gearth.protocol.HMessage; import gearth.protocol.connection.*; import gearth.protocol.connection.proxy.nitro.NitroConstants; import gearth.protocol.connection.proxy.nitro.NitroProxyProvider; -import gearth.services.internal_extensions.uilogger.hexdumper.Hexdump; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; @@ -45,7 +44,7 @@ public class NitroWebsocketClient implements NitroSession { this.server.getPacketHandler(), this.packetHandler, NitroConstants.WEBSOCKET_REVISION, - "HTML5" // TODO: What is its purpose? + NitroConstants.WEBSOCKET_CLIENT_IDENTIFIER ); proxySetter.setProxy(proxy); diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java index d162347..11122d1 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java @@ -28,7 +28,7 @@ public class NitroWebsocketProxy { this.stateSetter = stateSetter; this.connection = connection; this.proxyProvider = proxyProvider; - this.server = new Server(NitroConstants.PORT_WEBSOCKET); + this.server = new Server(NitroConstants.WEBSOCKET_PORT); } public boolean start() { From ae4ccff20ec9cc12a0618177924b4d5186fac55e Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 18:51:31 +0100 Subject: [PATCH 08/21] Add proper shutdown mechanism for Nitro --- .../nitro/websocket/NitroPacketHandler.java | 9 +++- .../nitro/websocket/NitroWebsocketClient.java | 53 ++++++++++++++++--- .../nitro/websocket/NitroWebsocketServer.java | 33 +++++++++--- 3 files changed, 80 insertions(+), 15 deletions(-) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java index 8ea0ee8..d6ed58a 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java @@ -6,6 +6,7 @@ import gearth.protocol.packethandler.PacketHandler; import gearth.services.extension_handler.ExtensionHandler; import gearth.services.extension_handler.OnHMessageHandled; +import javax.websocket.Session; import java.io.IOException; import java.nio.ByteBuffer; @@ -22,12 +23,18 @@ public class NitroPacketHandler extends PacketHandler { @Override public boolean sendToStream(byte[] buffer) { + final Session localSession = session.getSession(); + + if (localSession == null) { + return false; + } + // Required to prevent garbage buffer within the UI logger. if (direction == HMessage.Direction.TOSERVER) { buffer = buffer.clone(); } - session.getSession().getAsyncRemote().sendBinary(ByteBuffer.wrap(buffer)); + localSession.getAsyncRemote().sendBinary(ByteBuffer.wrap(buffer)); return true; } 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 ab890da..aabb946 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 @@ -9,6 +9,7 @@ import gearth.protocol.connection.proxy.nitro.NitroProxyProvider; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; @ServerEndpoint(value = "/") public class NitroWebsocketClient implements NitroSession { @@ -19,9 +20,9 @@ public class NitroWebsocketClient implements NitroSession { private final NitroProxyProvider proxyProvider; private final NitroWebsocketServer server; private final NitroPacketHandler packetHandler; + private final AtomicBoolean shutdownLock; private Session activeSession = null; - private HProxy proxy = null; public NitroWebsocketClient(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection, NitroProxyProvider proxyProvider) { this.proxySetter = proxySetter; @@ -30,6 +31,7 @@ public class NitroWebsocketClient implements NitroSession { this.proxyProvider = proxyProvider; this.server = new NitroWebsocketServer(connection, this); this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOSERVER, server, connection.getExtensionHandler(), connection.getTrafficObservables()); + this.shutdownLock = new AtomicBoolean(); } @OnOpen @@ -39,7 +41,8 @@ public class NitroWebsocketClient implements NitroSession { server.connect(proxyProvider.getOriginalWebsocketUrl()); - proxy = new HProxy(HClient.NITRO, "", "", -1, -1, ""); + final HProxy proxy = new HProxy(HClient.NITRO, "", "", -1, -1, ""); + proxy.verifyProxy( this.server.getPacketHandler(), this.packetHandler, @@ -53,25 +56,63 @@ public class NitroWebsocketClient implements NitroSession { @OnMessage public void onMessage(byte[] b, Session session) throws IOException { - System.out.printf("onMessage (%d)%n", b.length); - System.out.println(session); - System.out.println(Hexdump.hexdump(b)); - packetHandler.act(b); } @OnClose public void onClose(Session session) throws IOException { activeSession = null; + shutdownProxy(); } @OnError public void onError(Session session, Throwable throwable) { + throwable.printStackTrace(); + // Shutdown. + shutdownProxy(); } @Override public Session getSession() { return activeSession; } + + /** + * Shutdown and clean up the client connection. + */ + private void shutdown() { + if (activeSession == null) { + return; + } + + try { + activeSession.close(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + activeSession = null; + } + } + + /** + * Shutdown all connections and reset program state. + */ + public void shutdownProxy() { + if (shutdownLock.get()) { + return; + } + + if (shutdownLock.compareAndSet(false, true)) { + // Close client connection. + shutdown(); + + // Close server connection. + server.shutdown(); + + // Reset program state. + proxySetter.setProxy(null); + stateSetter.setState(HState.NOT_CONNECTED); + } + } } 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 f7928df..04b238c 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 @@ -13,9 +13,11 @@ import java.net.URI; public class NitroWebsocketServer implements NitroSession { private final PacketHandler packetHandler; + private final NitroWebsocketClient client; private Session activeSession = null; public NitroWebsocketServer(HConnection connection, NitroWebsocketClient client) { + this.client = client; this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOCLIENT, client, connection.getExtensionHandler(), connection.getTrafficObservables()); } @@ -35,23 +37,21 @@ public class NitroWebsocketServer implements NitroSession { @OnMessage public void onMessage(byte[] b, Session session) throws IOException { - //System.out.printf("onMessage (%d)%n", b.length); - //System.out.println(session); - //System.out.println(Hexdump.hexdump(b)); - packetHandler.act(b); } @OnClose public void onClose(Session userSession, CloseReason reason) { - //System.out.println("closing websocket"); + // Hotel closed connection. + client.shutdownProxy(); } @OnError public void onError(Session session, Throwable throwable) { - //System.out.println("onError"); - //System.out.println(session); - //System.out.println(throwable); + throwable.printStackTrace(); + + // Shutdown. + client.shutdownProxy(); } @Override @@ -62,4 +62,21 @@ public class NitroWebsocketServer implements NitroSession { public PacketHandler getPacketHandler() { return packetHandler; } + + /** + * Shutdown and clean up the server connection. + */ + public void shutdown() { + if (activeSession == null) { + return; + } + + try { + activeSession.close(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + activeSession = null; + } + } } From 322cb05ceb31cfa5ab8ab713336f15d8d2f66adf Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 18:52:29 +0100 Subject: [PATCH 09/21] Remove unused class --- .../proxy/nitro/websocket/NitroForwarder.java | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroForwarder.java diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroForwarder.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroForwarder.java deleted file mode 100644 index 42433da..0000000 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroForwarder.java +++ /dev/null @@ -1,17 +0,0 @@ -package gearth.protocol.connection.proxy.nitro.websocket; - -import gearth.protocol.HConnection; - -public class NitroForwarder { - - private final HConnection connection; - private final NitroSession client; - private final NitroSession server; - - public NitroForwarder(HConnection connection, NitroSession client, NitroSession server) { - this.connection = connection; - this.client = client; - this.server = server; - } - -} From 09105e956b1ee7503c9be1141f6935f2036631b2 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 19:58:34 +0100 Subject: [PATCH 10/21] Prompt root certificate installation --- .../main/java/gearth/misc/RuntimeUtil.java | 31 ++++++++++++ .../proxy/nitro/http/NitroHttpProxy.java | 47 +++++++++++++++++++ .../proxy/nitro/os/NitroOsFunctions.java | 2 + .../proxy/nitro/os/windows/NitroWindows.java | 45 +++++++++++++++++- .../nitro/os/windows/NitroWindowsShell32.java | 17 +++++++ 5 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 G-Earth/src/main/java/gearth/misc/RuntimeUtil.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindowsShell32.java diff --git a/G-Earth/src/main/java/gearth/misc/RuntimeUtil.java b/G-Earth/src/main/java/gearth/misc/RuntimeUtil.java new file mode 100644 index 0000000..0e17736 --- /dev/null +++ b/G-Earth/src/main/java/gearth/misc/RuntimeUtil.java @@ -0,0 +1,31 @@ +package gearth.misc; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public final class RuntimeUtil { + + public static String getCommandOutput(String[] command) throws IOException { + try { + final Runtime rt = Runtime.getRuntime(); + final Process proc = rt.exec(command); + + final BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream())); + final StringBuilder result = new StringBuilder(); + + String line; + + while ((line = stdInput.readLine()) != null) { + result.append(line); + result.append("\n"); + } + + return result.toString(); + } catch (IOException e) { + e.printStackTrace(); + throw e; + } + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java index 84885ee..475d912 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java @@ -1,16 +1,26 @@ package gearth.protocol.connection.proxy.nitro.http; +import gearth.misc.ConfirmationDialog; import gearth.protocol.connection.proxy.nitro.NitroConstants; import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions; import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctionsFactory; +import javafx.application.Platform; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; import org.littleshoot.proxy.HttpProxyServer; import org.littleshoot.proxy.impl.DefaultHttpProxyServer; import org.littleshoot.proxy.mitm.Authority; import org.littleshoot.proxy.mitm.CertificateSniffingMitmManager; import org.littleshoot.proxy.mitm.RootCertificateException; +import java.io.File; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; + public class NitroHttpProxy { + private static final String ADMIN_WARNING_KEY = "admin_warning_dialog"; + private final Authority authority; private final NitroOsFunctions osFunctions; private final NitroHttpProxyServerCallback serverCallback; @@ -24,6 +34,43 @@ public class NitroHttpProxy { } private boolean initializeCertificate() { + final File certificate = this.authority.aliasFile(".pem"); + + // All good if certificate is already trusted. + if (this.osFunctions.isRootCertificateTrusted(certificate)) { + return true; + } + + // Let the user know about admin permissions. + final Semaphore waitForDialog = new Semaphore(0); + final AtomicBoolean shouldInstall = new AtomicBoolean(); + + Platform.runLater(() -> { + Alert alert = ConfirmationDialog.createAlertWithOptOut(Alert.AlertType.WARNING, ADMIN_WARNING_KEY, + "Root certificate installation", null, + "G-Earth detected that you do not have the root certificate authority installed. " + + "This is required for Nitro to work, do you want to continue? " + + "G-Earth will ask you for Administrator permission if you do so.", "Remember my choice", + ButtonType.YES, ButtonType.NO + ); + + shouldInstall.set(!(alert.showAndWait().filter(t -> t == ButtonType.YES).isPresent())); + waitForDialog.release(); + }); + + // Wait for dialog choice. + try { + waitForDialog.acquire(); + } catch (InterruptedException e) { + e.printStackTrace(); + return false; + } + + // User opted out. + if (!shouldInstall.get()) { + return false; + } + return this.osFunctions.installRootCertificate(this.authority.aliasFile(".pem")); } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java index d77a4c6..0bfaf29 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java @@ -4,6 +4,8 @@ import java.io.File; public interface NitroOsFunctions { + boolean isRootCertificateTrusted(File certificate); + boolean installRootCertificate(File certificate); boolean registerSystemProxy(String host, int port); diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java index 27c7b12..fc5abf5 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java @@ -1,15 +1,56 @@ package gearth.protocol.connection.proxy.nitro.os.windows; +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.platform.win32.WinBase; +import com.sun.jna.platform.win32.WinDef; +import com.sun.jna.ptr.IntByReference; +import gearth.misc.RuntimeUtil; import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions; import java.io.File; +import java.io.IOException; public class NitroWindows implements NitroOsFunctions { + /** + * Checks if the certificate is trusted by the local machine. + * @param certificate Absolute path to the certificate. + * @return true if trusted + */ + @Override + public boolean isRootCertificateTrusted(File certificate) { + try { + final String output = RuntimeUtil.getCommandOutput(new String[] {"cmd", "/c", " certutil.exe -f -verify " + certificate.getAbsolutePath()}); + + return !output.contains("CERT_TRUST_IS_UNTRUSTED_ROOT") && + output.contains("dwInfoStatus=10c dwErrorStatus=0"); + } catch (IOException e) { + e.printStackTrace(); + } + + return false; + } + @Override public boolean installRootCertificate(File certificate) { - // TODO: Prompt registration - System.out.println(certificate.toString()); + final String certificatePath = certificate.getAbsolutePath(); + + // Prompt UAC elevation. + WinDef.HINSTANCE result = NitroWindowsShell32.INSTANCE.ShellExecuteA(null, "runas", "cmd.exe", "/S /C \"certutil -addstore root " + certificatePath + "\"", null, 1); + + // Wait for exit. + Kernel32.INSTANCE.WaitForSingleObject(result, WinBase.INFINITE); + + // Exit code for certutil. + final IntByReference statusRef = new IntByReference(-1); + Kernel32.INSTANCE.GetExitCodeProcess(result, statusRef); + + // Check if process exited without errors + if (statusRef.getValue() != -1) { + System.out.printf("Certutil command exited with exit code %s%n", statusRef.getValue()); + return false; + } + return true; } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindowsShell32.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindowsShell32.java new file mode 100644 index 0000000..69087aa --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindowsShell32.java @@ -0,0 +1,17 @@ +package gearth.protocol.connection.proxy.nitro.os.windows; + +import com.sun.jna.Native; +import com.sun.jna.platform.win32.ShellAPI; +import com.sun.jna.platform.win32.WinDef; +import com.sun.jna.win32.StdCallLibrary; + +public interface NitroWindowsShell32 extends ShellAPI, StdCallLibrary { + NitroWindowsShell32 INSTANCE = Native.loadLibrary("shell32", NitroWindowsShell32.class); + + WinDef.HINSTANCE ShellExecuteA(WinDef.HWND hwnd, + String lpOperation, + String lpFile, + String lpParameters, + String lpDirectory, + int nShowCmd); +} From cc7dece9a48c78556847c3350653354d60618f43 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 20:07:42 +0100 Subject: [PATCH 11/21] Add shutdown hook to ensure system proxy is unregistered --- .../proxy/nitro/http/NitroHttpProxy.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java index 475d912..231abb7 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java @@ -20,6 +20,7 @@ import java.util.concurrent.atomic.AtomicBoolean; public class NitroHttpProxy { private static final String ADMIN_WARNING_KEY = "admin_warning_dialog"; + private static final AtomicBoolean SHUTDOWN_HOOK = new AtomicBoolean(); private final Authority authority; private final NitroOsFunctions osFunctions; @@ -89,6 +90,8 @@ public class NitroHttpProxy { } public boolean start() { + setupShutdownHook(); + try { proxyServer = DefaultHttpProxyServer.bootstrap() .withPort(NitroConstants.HTTP_PORT) @@ -133,4 +136,18 @@ public class NitroHttpProxy { proxyServer.stop(); proxyServer = null; } + + /** + * Ensure the system proxy is removed when G-Earth exits. + * Otherwise, users might complain that their browsers / discord stop working when closing G-Earth incorrectly. + */ + private static void setupShutdownHook() { + if (SHUTDOWN_HOOK.get()) { + return; + } + + if (SHUTDOWN_HOOK.compareAndSet(false, true)) { + Runtime.getRuntime().addShutdownHook(new Thread(() -> NitroOsFunctionsFactory.create().unregisterSystemProxy())); + } + } } From dd0aeeb1db46aed4d389a0f71a15431dc3f71134 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 20:20:51 +0100 Subject: [PATCH 12/21] Modify content-security-policy header --- .../nitro/http/NitroHttpProxyFilter.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java index 28a33ae..ea0cddc 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java @@ -13,11 +13,13 @@ import java.util.regex.Pattern; public class NitroHttpProxyFilter extends HttpFiltersAdapter { private static final String NitroConfigSearch = "\"socket.url\""; + private static final String NitroClientSearch = "configurationUrls:"; private static final Pattern NitroConfigPattern = Pattern.compile("\"socket\\.url\":.?\"(wss?://.*?)\"", Pattern.MULTILINE); private static final String HeaderAcceptEncoding = "Accept-Encoding"; private static final String HeaderAge = "Age"; private static final String HeaderCacheControl = "Cache-Control"; + private static final String HeaderContentSecurityPolicy = "Content-Security-Policy"; private static final String HeaderETag = "ETag"; private static final String HeaderIfNoneMatch = "If-None-Match"; private static final String HeaderIfModifiedSince = "If-Modified-Since"; @@ -88,11 +90,37 @@ public class NitroHttpProxyFilter extends HttpFiltersAdapter { if (responseModified) { responseWrite(response, responseBody); } + + // CSP. + if (responseBody.contains(NitroClientSearch)) { + stripContentSecurityPolicy(response); + } } return httpObject; } + /** + * Modify Content-Security-Policy header, which could prevent Nitro from connecting with G-Earth. + */ + private void stripContentSecurityPolicy(FullHttpResponse response) { + final HttpHeaders headers = response.headers(); + + if (!headers.contains(HeaderContentSecurityPolicy)){ + return; + } + + String csp = headers.get(HeaderContentSecurityPolicy); + + if (csp.contains("connect-src")) { + csp = csp.replace("connect-src", "connect-src *"); + } else if (csp.contains("default-src")) { + csp = csp.replace("default-src", "default-src *"); + } + + headers.set(HeaderContentSecurityPolicy, csp); + } + private static String responseRead(FullHttpResponse response) { final ByteBuf contentBuf = response.content(); return contentBuf.toString(CharsetUtil.UTF_8); From 4681b916eb56feeeb5696bebb359850beec99006 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 20:25:12 +0100 Subject: [PATCH 13/21] Add ignored hosts for proxy --- .../connection/proxy/nitro/os/windows/NitroWindows.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java index fc5abf5..049a6be 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java @@ -12,6 +12,11 @@ import java.io.IOException; public class NitroWindows implements NitroOsFunctions { + /** + * Semicolon separated hosts to ignore for proxying. + */ + private static final String PROXY_IGNORE = "discord.com;github.com;"; + /** * Checks if the certificate is trusted by the local machine. * @param certificate Absolute path to the certificate. @@ -59,6 +64,7 @@ public class NitroWindows implements NitroOsFunctions { try { final String proxy = String.format("%s:%d", host, port); Runtime.getRuntime().exec("reg add \"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v ProxyServer /t REG_SZ /d \"" + proxy + "\" /f"); + Runtime.getRuntime().exec("reg add \"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v ProxyOverride /t REG_SZ /d \"" + PROXY_IGNORE + "\" /f"); Runtime.getRuntime().exec("reg add \"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v ProxyEnable /t REG_DWORD /d 1 /f"); return true; } catch (Exception e) { From bfe13966452e84406589098259c54843dd872363 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 20:27:04 +0100 Subject: [PATCH 14/21] Do not select nitro by default --- G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml b/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml index bf7cb35..16982b0 100644 --- a/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml +++ b/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml @@ -158,7 +158,7 @@ - + From f381de6faf40ba3398b9a158e9d0ce7296854f01 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Tue, 30 Nov 2021 18:47:05 +0100 Subject: [PATCH 15/21] Add certificate sniffing manager code --- .../http/NitroCertificateSniffingManager.java | 94 +++++++++++++++++++ .../proxy/nitro/http/NitroHttpProxy.java | 2 +- .../proxy/nitro/os/windows/NitroWindows.java | 2 +- 3 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroCertificateSniffingManager.java diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroCertificateSniffingManager.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroCertificateSniffingManager.java new file mode 100644 index 0000000..d6d0c60 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroCertificateSniffingManager.java @@ -0,0 +1,94 @@ +package gearth.protocol.connection.proxy.nitro.http; + +import io.netty.handler.codec.http.HttpRequest; +import org.littleshoot.proxy.MitmManager; +import org.littleshoot.proxy.mitm.*; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +/** + * {@link MitmManager} that uses the common name and subject alternative names + * from the upstream certificate to create a dynamic certificate with it. + */ +public class NitroCertificateSniffingManager implements MitmManager { + + private static final boolean DEBUG = false; + + private BouncyCastleSslEngineSource sslEngineSource; + + public NitroCertificateSniffingManager(Authority authority) throws RootCertificateException { + try { + sslEngineSource = new BouncyCastleSslEngineSource(authority, true, true); + } catch (final Exception e) { + throw new RootCertificateException("Errors during assembling root CA.", e); + } + } + + public SSLEngine serverSslEngine(String peerHost, int peerPort) { + return sslEngineSource.newSslEngine(peerHost, peerPort); + } + + public SSLEngine serverSslEngine() { + return sslEngineSource.newSslEngine(); + } + + public SSLEngine clientSslEngineFor(HttpRequest httpRequest, SSLSession serverSslSession) { + try { + X509Certificate upstreamCert = getCertificateFromSession(serverSslSession); + // TODO store the upstream cert by commonName to review it later + + // A reasons to not use the common name and the alternative names + // from upstream certificate from serverSslSession to create the + // dynamic certificate: + // + // It's not necessary. The host name is accepted by the browser. + // + String commonName = getCommonName(upstreamCert); + + SubjectAlternativeNameHolder san = new SubjectAlternativeNameHolder(); + + san.addAll(upstreamCert.getSubjectAlternativeNames()); + + if (DEBUG) { + System.out.printf("[NitroCertificateSniffingManager] Subject Alternative Names: %s%n", san); + } + + return sslEngineSource.createCertForHost(commonName, san); + } catch (Exception e) { + throw new FakeCertificateException("Creation dynamic certificate failed", e); + } + } + + private X509Certificate getCertificateFromSession(SSLSession sslSession) throws SSLPeerUnverifiedException { + Certificate[] peerCerts = sslSession.getPeerCertificates(); + Certificate peerCert = peerCerts[0]; + if (peerCert instanceof java.security.cert.X509Certificate) { + return (java.security.cert.X509Certificate) peerCert; + } + throw new IllegalStateException("Required java.security.cert.X509Certificate, found: " + peerCert); + } + + private String getCommonName(X509Certificate c) { + if (DEBUG) { + System.out.printf("[NitroCertificateSniffingManager] Subject DN principal name: %s%n", c.getSubjectDN().getName()); + } + + for (String each : c.getSubjectDN().getName().split(",\\s*")) { + if (each.startsWith("CN=")) { + String result = each.substring(3); + + if (DEBUG) { + System.out.printf("[NitroCertificateSniffingManager] Common Name: %s%n", c.getSubjectDN().getName()); + } + + return result; + } + } + + throw new IllegalStateException("Missed CN in Subject DN: " + c.getSubjectDN()); + } +} \ No newline at end of file diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java index 231abb7..7219b8f 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java @@ -95,7 +95,7 @@ public class NitroHttpProxy { try { proxyServer = DefaultHttpProxyServer.bootstrap() .withPort(NitroConstants.HTTP_PORT) - .withManInTheMiddle(new CertificateSniffingMitmManager(authority)) + .withManInTheMiddle(new NitroCertificateSniffingManager(authority)) .withFiltersSource(new NitroHttpProxyFilterSource(serverCallback)) .start(); diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java index 049a6be..c5f4655 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java @@ -15,7 +15,7 @@ public class NitroWindows implements NitroOsFunctions { /** * Semicolon separated hosts to ignore for proxying. */ - private static final String PROXY_IGNORE = "discord.com;github.com;"; + private static final String PROXY_IGNORE = "discord.com;discordapp.com;github.com;"; /** * Checks if the certificate is trusted by the local machine. From 2de16e426412fe9a1772d08604b491f827a569fd Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Tue, 30 Nov 2021 19:14:34 +0100 Subject: [PATCH 16/21] Disable BouncyCastleSslEngineSource cache --- .../nitro/http/NitroCertificateSniffingManager.java | 11 ++++++++--- .../connection/proxy/nitro/http/NitroHttpProxy.java | 1 - 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroCertificateSniffingManager.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroCertificateSniffingManager.java index d6d0c60..35f4c2d 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroCertificateSniffingManager.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroCertificateSniffingManager.java @@ -9,6 +9,7 @@ import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import java.security.cert.Certificate; import java.security.cert.X509Certificate; +import java.util.List; /** * {@link MitmManager} that uses the common name and subject alternative names @@ -18,11 +19,11 @@ public class NitroCertificateSniffingManager implements MitmManager { private static final boolean DEBUG = false; - private BouncyCastleSslEngineSource sslEngineSource; + private final BouncyCastleSslEngineSource sslEngineSource; public NitroCertificateSniffingManager(Authority authority) throws RootCertificateException { try { - sslEngineSource = new BouncyCastleSslEngineSource(authority, true, true); + sslEngineSource = new BouncyCastleSslEngineSource(authority, true, true, null); } catch (final Exception e) { throw new RootCertificateException("Errors during assembling root CA.", e); } @@ -54,7 +55,11 @@ public class NitroCertificateSniffingManager implements MitmManager { san.addAll(upstreamCert.getSubjectAlternativeNames()); if (DEBUG) { - System.out.printf("[NitroCertificateSniffingManager] Subject Alternative Names: %s%n", san); + System.out.println("[NitroCertificateSniffingManager] Subject Alternative Names"); + + for (List name : upstreamCert.getSubjectAlternativeNames()) { + System.out.printf("[NitroCertificateSniffingManager] - %s%n", name.toString()); + } } return sslEngineSource.createCertForHost(commonName, san); diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java index 7219b8f..544b2ba 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java @@ -10,7 +10,6 @@ import javafx.scene.control.ButtonType; import org.littleshoot.proxy.HttpProxyServer; import org.littleshoot.proxy.impl.DefaultHttpProxyServer; import org.littleshoot.proxy.mitm.Authority; -import org.littleshoot.proxy.mitm.CertificateSniffingMitmManager; import org.littleshoot.proxy.mitm.RootCertificateException; import java.io.File; From ad9728af288554a713afd77db701a689308113f5 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Tue, 30 Nov 2021 20:43:48 +0100 Subject: [PATCH 17/21] Fix confirmation dialog yes/no --- .../protocol/connection/proxy/nitro/http/NitroHttpProxy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java index 544b2ba..cc897a6 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java @@ -54,7 +54,7 @@ public class NitroHttpProxy { ButtonType.YES, ButtonType.NO ); - shouldInstall.set(!(alert.showAndWait().filter(t -> t == ButtonType.YES).isPresent())); + shouldInstall.set(alert.showAndWait().filter(t -> t == ButtonType.YES).isPresent()); waitForDialog.release(); }); From 58fccbd9317b24ab204966582d6bc35aa6c8d7ea Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Tue, 30 Nov 2021 20:44:20 +0100 Subject: [PATCH 18/21] Added github workflow --- .github/workflows/build.yml | 88 +++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..6ba3356 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,88 @@ +name: Build G-Earth + +on: + push: + paths: + - '.github/workflows/**' + - 'G-Earth/**' + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout G-Earth + uses: actions/checkout@v2 + + - name: Checkout G-Wasm + uses: actions/checkout@v2 + with: + repository: sirjonasxx/G-Wasm + path: gwasm + ref: minimal + + - name: Set up JDK 8 + uses: actions/setup-java@v2 + with: + java-version: '8' + java-package: jdk+fx + distribution: 'liberica' + + - name: Install G-Wasm + working-directory: gwasm + run: mvn -B install + + - name: Build G-Earth + run: mvn -B package + + - name: Zip Build/Mac + run: | + cd ${{ github.workspace }}/Build/Mac/ + zip -r ../../build-mac.zip * + + - name: Zip Build/Linux + run: | + cd ${{ github.workspace }}/Build/Linux/ + zip -r ../../build-linux.zip * + + - name: Zip Build/Windows_32bit + run: | + cd ${{ github.workspace }}/Build/Windows_32bit/ + zip -r ../../build-win32.zip * + + - name: Zip Build/Windows_64bit + run: | + cd ${{ github.workspace }}/Build/Windows_64bit/ + zip -r ../../build-win64.zip * + + - name: Upload Mac OSX + uses: actions/upload-artifact@v2 + with: + name: Mac OSX + path: build-mac.zip + retention-days: 7 + + - name: Upload Linux + uses: actions/upload-artifact@v2 + with: + name: Linux + path: build-linux.zip + retention-days: 7 + + - name: Upload Windows x32 + uses: actions/upload-artifact@v2 + with: + name: Windows x32 + path: build-win32.zip + retention-days: 7 + + - name: Upload Windows x64 + uses: actions/upload-artifact@v2 + with: + name: Windows x64 + path: build-win64.zip + retention-days: 7 \ No newline at end of file From a525d6c8673e0c7a9e47fd75054398dcfd7771f4 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Wed, 1 Dec 2021 20:51:11 +0100 Subject: [PATCH 19/21] Properly combine packets --- .../nitro/websocket/NitroPacketHandler.java | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java index d6ed58a..70ed0cb 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java @@ -3,6 +3,7 @@ package gearth.protocol.connection.proxy.nitro.websocket; import gearth.protocol.HMessage; import gearth.protocol.HPacket; import gearth.protocol.packethandler.PacketHandler; +import gearth.protocol.packethandler.PayloadBuffer; import gearth.services.extension_handler.ExtensionHandler; import gearth.services.extension_handler.OnHMessageHandled; @@ -14,11 +15,15 @@ public class NitroPacketHandler extends PacketHandler { private final HMessage.Direction direction; private final NitroSession session; + private final PayloadBuffer payloadBuffer; + private final Object payloadLock; protected NitroPacketHandler(HMessage.Direction direction, NitroSession session, ExtensionHandler extensionHandler, Object[] trafficObservables) { super(extensionHandler, trafficObservables); this.direction = direction; this.session = session; + this.payloadBuffer = new PayloadBuffer(); + this.payloadLock = new Object(); } @Override @@ -40,20 +45,26 @@ public class NitroPacketHandler extends PacketHandler { @Override public void act(byte[] buffer) throws IOException { - HMessage hMessage = new HMessage(new HPacket(buffer), direction, currentIndex); + payloadBuffer.push(buffer); - OnHMessageHandled afterExtensionIntercept = hMessage1 -> { - notifyListeners(2, hMessage1); + synchronized (payloadLock) { + for (HPacket packet : payloadBuffer.receive()) { + HMessage hMessage = new HMessage(packet, direction, currentIndex); - if (!hMessage1.isBlocked()) { - sendToStream(hMessage1.getPacket().toBytes()); + OnHMessageHandled afterExtensionIntercept = hMessage1 -> { + notifyListeners(2, hMessage1); + + if (!hMessage1.isBlocked()) { + sendToStream(hMessage1.getPacket().toBytes()); + } + }; + + notifyListeners(0, hMessage); + notifyListeners(1, hMessage); + extensionHandler.handle(hMessage, afterExtensionIntercept); + + currentIndex++; } - }; - - notifyListeners(0, hMessage); - notifyListeners(1, hMessage); - extensionHandler.handle(hMessage, afterExtensionIntercept); - - currentIndex++; + } } } From f49906897c646bd2abdf9e5c1d2eb5714b2fee18 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Wed, 1 Dec 2021 23:09:50 +0100 Subject: [PATCH 20/21] Extract origin url from config path --- .../proxy/nitro/NitroProxyProvider.java | 20 ++++++++ .../nitro/websocket/NitroWebsocketClient.java | 2 +- .../nitro/websocket/NitroWebsocketServer.java | 50 +++++++++++++------ 3 files changed, 56 insertions(+), 16 deletions(-) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java index 3cb47a1..b8c0810 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java @@ -11,6 +11,8 @@ import gearth.protocol.connection.proxy.nitro.http.NitroHttpProxyServerCallback; import gearth.protocol.connection.proxy.nitro.websocket.NitroWebsocketProxy; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCallback, StateChangeListener { @@ -21,6 +23,7 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa private final NitroWebsocketProxy nitroWebsocketProxy; private String originalWebsocketUrl; + private String originalOriginUrl; public NitroProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection) { this.proxySetter = proxySetter; @@ -34,6 +37,10 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa return originalWebsocketUrl; } + public String getOriginalOriginUrl() { + return originalOriginUrl; + } + @Override public void start() throws IOException { connection.getStateObservable().addListener(this); @@ -79,6 +86,8 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa @Override public String replaceWebsocketServer(String configUrl, String websocketUrl) { originalWebsocketUrl = websocketUrl; + originalOriginUrl = extractOriginUrl(configUrl); + return String.format("ws://127.0.0.1:%d", NitroConstants.WEBSOCKET_PORT); } @@ -90,4 +99,15 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa nitroHttpProxy.pause(); } } + + private static String extractOriginUrl(String url) { + try { + final URI uri = new URI(url); + return String.format("%s://%s/", uri.getScheme(), uri.getHost()); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + + return null; + } } 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 aabb946..59bf027 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 @@ -39,7 +39,7 @@ public class NitroWebsocketClient implements NitroSession { activeSession = session; activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE); - server.connect(proxyProvider.getOriginalWebsocketUrl()); + server.connect(proxyProvider.getOriginalWebsocketUrl(), proxyProvider.getOriginalOriginUrl()); final HProxy proxy = new HProxy(HClient.NITRO, "", "", -1, -1, ""); 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 04b238c..b54b96c 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 @@ -8,9 +8,11 @@ import gearth.protocol.packethandler.PacketHandler; import javax.websocket.*; import java.io.IOException; import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.Map; -@ClientEndpoint -public class NitroWebsocketServer implements NitroSession { +public class NitroWebsocketServer extends Endpoint implements NitroSession { private final PacketHandler packetHandler; private final NitroWebsocketClient client; @@ -21,32 +23,50 @@ public class NitroWebsocketServer implements NitroSession { this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOCLIENT, client, connection.getExtensionHandler(), connection.getTrafficObservables()); } - public void connect(String websocketUrl) throws IOException { + public void connect(String websocketUrl, String originUrl) throws IOException { try { - ContainerProvider.getWebSocketContainer().connectToServer(this, URI.create(websocketUrl)); + ClientEndpointConfig.Builder builder = ClientEndpointConfig.Builder.create(); + + if (originUrl != null) { + builder.configurator(new ClientEndpointConfig.Configurator() { + @Override + public void beforeRequest(Map> headers) { + headers.put("Origin", Collections.singletonList(originUrl)); + } + }); + } + + ClientEndpointConfig config = builder.build(); + + ContainerProvider.getWebSocketContainer().connectToServer(this, config, URI.create(websocketUrl)); } catch (DeploymentException e) { throw new IOException("Failed to deploy websocket client", e); } } - @OnOpen - public void onOpen(Session Session) { - this.activeSession = Session; + @Override + public void onOpen(Session session, EndpointConfig config) { + this.activeSession = session; this.activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE); + this.activeSession.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(byte[] message) { + try { + packetHandler.act(message); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); } - @OnMessage - public void onMessage(byte[] b, Session session) throws IOException { - packetHandler.act(b); - } - - @OnClose - public void onClose(Session userSession, CloseReason reason) { + @Override + public void onClose(Session session, CloseReason closeReason) { // Hotel closed connection. client.shutdownProxy(); } - @OnError + @Override public void onError(Session session, Throwable throwable) { throwable.printStackTrace(); From e0cb283ab756df5a95bbb6647656e9072835054e Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Wed, 1 Dec 2021 23:38:33 +0100 Subject: [PATCH 21/21] Properly abort when client closes connection --- .../proxy/nitro/NitroProxyProvider.java | 16 ++++++++++++++++ .../nitro/websocket/NitroWebsocketClient.java | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java index b8c0810..5b90922 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java @@ -13,6 +13,7 @@ import gearth.protocol.connection.proxy.nitro.websocket.NitroWebsocketProxy; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.util.concurrent.atomic.AtomicBoolean; public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCallback, StateChangeListener { @@ -21,6 +22,7 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa private final HConnection connection; private final NitroHttpProxy nitroHttpProxy; private final NitroWebsocketProxy nitroWebsocketProxy; + private final AtomicBoolean abortLock; private String originalWebsocketUrl; private String originalOriginUrl; @@ -31,6 +33,7 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa this.connection = connection; this.nitroHttpProxy = new NitroHttpProxy(this); this.nitroWebsocketProxy = new NitroWebsocketProxy(proxySetter, stateSetter, connection, this); + this.abortLock = new AtomicBoolean(); } public String getOriginalWebsocketUrl() { @@ -62,6 +65,14 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa @Override public void abort() { + if (abortLock.get()) { + return; + } + + if (abortLock.compareAndSet(true, true)) { + return; + } + stateSetter.setState(HState.ABORTING); new Thread(() -> { @@ -98,6 +109,11 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa // We are not stopping the http proxy because some requests might still require it to be running. nitroHttpProxy.pause(); } + + // Catch setState ABORTING inside NitroWebsocketClient. + if (newState == HState.ABORTING) { + abort(); + } } private static String extractOriginUrl(String url) { 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 59bf027..ca7e88b 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 @@ -112,7 +112,7 @@ public class NitroWebsocketClient implements NitroSession { // Reset program state. proxySetter.setProxy(null); - stateSetter.setState(HState.NOT_CONNECTED); + stateSetter.setState(HState.ABORTING); } } }