diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java b/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java index d18d2e3b9..dd5bf88e7 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java @@ -59,7 +59,7 @@ import tw.nekomimi.nekogram.proxy.ProxyManager; import tw.nekomimi.nekogram.proxy.ShadowsocksLoader; import tw.nekomimi.nekogram.proxy.ShadowsocksRLoader; import tw.nekomimi.nekogram.proxy.VmessLoader; -import tw.nekomimi.nekogram.proxy.WsLoader; +import tw.nekomimi.nekogram.proxy.tcp2ws.WsLoader; import tw.nekomimi.nekogram.proxy.SubInfo; import tw.nekomimi.nekogram.proxy.SubManager; import tw.nekomimi.nekogram.utils.AlertUtil; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java index b9caae22d..5e57995d6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java @@ -912,29 +912,19 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar MediaController.getInstance().setBaseActivity(this, true); UIUtil.runOnIoDispatcher(() -> { - - ExternalGcm.checkUpdate(this); - - if (NekoConfig.autoUpdateSubInfo.Bool()) for (SubInfo subInfo : SubManager.getSubList().find()) { - - if (subInfo == null || !subInfo.enable) continue; - - try { - - subInfo.proxies = subInfo.reloadProxies(); - subInfo.lastFetch = System.currentTimeMillis(); - - SubManager.getSubList().update(subInfo, true); - SharedConfig.reloadProxyList(); - - } catch (IOException allTriesFailed) { - - FileLog.e(allTriesFailed); - +// ExternalGcm.checkUpdate(this); + if (NekoConfig.autoUpdateSubInfo.Bool()) + for (SubInfo subInfo : SubManager.getSubList().find()) { + if (subInfo == null || !subInfo.enable) continue; + try { + subInfo.proxies = subInfo.reloadProxies(); + subInfo.lastFetch = System.currentTimeMillis(); + SubManager.getSubList().update(subInfo, true); + SharedConfig.reloadProxyList(); + } catch (IOException allTriesFailed) { + FileLog.e(allTriesFailed); + } } - - } - }); //FileLog.d("UI create time = " + (SystemClock.elapsedRealtime() - ApplicationLoader.startTime)); diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/parts/ProxyLoads.kt b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/parts/ProxyLoads.kt index 8fb0cc91e..403b49eac 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/parts/ProxyLoads.kt +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/parts/ProxyLoads.kt @@ -20,20 +20,16 @@ fun loadProxiesPublic(urls: List, exceptions: MutableMap mapper; - WsLoader.Bean bean; - byte[] m_Buffer = new byte[SocksConstants.DEFAULT_BUF_SIZE]; - - public ProxyHandler(Socket clientSocket, HashMap mapper, WsLoader.Bean bean) { - this.mapper = mapper; - this.bean = bean; - this.m_ClientSocket = clientSocket; - try { - m_ClientSocket.setSoTimeout(SocksConstants.DEFAULT_PROXY_TIMEOUT); - } catch (SocketException e) { - FileLog.e("Socket Exception during seting Timeout."); - } - FileLog.d("Proxy Created."); - } - - public void setLock(Object lock) { - m_lock = lock; - } - - public void run() { - FileLog.d("Proxy Started."); - setLock(this); - - if (prepareClient()) { - processRelay(); - close(); - } else { - FileLog.e("Proxy - client socket is null !"); - } - } - - public void close() { - try { - if (m_ClientOutput != null) { - m_ClientOutput.flush(); - m_ClientOutput.close(); - } - } catch (IOException e) { - // ignore - } - - try { - if (m_ClientSocket != null) { - m_ClientSocket.close(); - } - } catch (IOException e) { - // ignore - } - - try { - if (m_ServerSocket != null) { - m_ServerSocket.close(1000, ""); - } - } catch (Exception e) { - // ignore - } - - m_ServerSocket = null; - m_ClientSocket = null; - m_ServerSocketRaw = null; - - FileLog.d("Proxy Closed."); - } - - public void sendToClient(byte[] buffer) { - sendToClient(buffer, buffer.length); - } - - public void sendToClient(byte[] buffer, int len) { - if (m_ClientOutput != null && len > 0 && len <= buffer.length) { - try { - m_ClientOutput.write(buffer, 0, len); - m_ClientOutput.flush(); - } catch (IOException e) { - FileLog.e("Sending data to client", e); - } - } - } - - public static Object okhttpClient; - - public void connectToServer(String server, Runnable succ, Runnable fail) throws IOException { - if (server.equals("")) { - close(); - FileLog.e("Invalid Remote Host Name - Empty String !!!"); - return; - } - - Integer target = mapper.get(server); - for (int i = 1; target == null && i < 4; i++) { - target = mapper.get(server.substring(0, server.length() - i)); - } - if (target == null || target.equals(-1)) { - // Too many logs - if (!mapper.containsKey(server)) { - mapper.put(server, -1); - FileLog.e("No route for ip " + server); - } - close(); - return; - } - - String ip = server; - - if (bean.getPayload().size() >= target) { - server = bean.getPayload().get(target - 1); - } - - if (BuildConfig.DEBUG) { - FileLog.d("Route " + ip + " to dc" + target + ": " + (bean.getTls() ? "wss://" : "ws://") + server + "." + bean.getServer() + "/api"); - } - - if (okhttpClient == null) { - okhttpClient = new OkHttpClient.Builder().dns(new WsLoader.CustomDns()).build(); - } - - ((OkHttpClient) okhttpClient) - .newWebSocket(new Request.Builder() - .url((bean.getTls() ? "wss://" : "ws://") + server + "." + bean.getServer() + "/api") - .build(), new WebSocketListener() { - @Override - public void onOpen(@NotNull okhttp3.WebSocket webSocket, @NotNull Response response) { - m_ServerSocket = webSocket; - m_ServerSocketRaw = NativeImageTestsAccessorsKt.getConnection(Objects.requireNonNull(NativeImageTestsAccessorsKt.getExchange(response))).socket(); - succ.run(); - } - - @Override - public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable t, @Nullable Response response) { - error = t; - fail.run(); - } - - @Override - public void onMessage(@NotNull okhttp3.WebSocket webSocket, @NotNull ByteString bytes) { - FileLog.d("[" + webSocket.request().url() + "] Reveived " + bytes.size() + " bytes"); - ProxyHandler.this.sendToClient(bytes.toByteArray()); - } - - @Override - public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) { - FileLog.d("[" + webSocket.request().url() + "] Reveived text: " + text); - } - - @Override - public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) { - FileLog.d("[" + webSocket.request().url() + "] Closed: " + code + " " + reason); - } - - @Override - public void onClosing(@NotNull WebSocket webSocket, int code, @NotNull String reason) { - FileLog.d("[" + webSocket.request().url() + "] Closing: " + code + " " + reason); - close(); - - } - }); - } - - public boolean prepareClient() { - if (m_ClientSocket == null) return false; - - try { - m_ClientInput = m_ClientSocket.getInputStream(); - m_ClientOutput = m_ClientSocket.getOutputStream(); - return true; - } catch (IOException e) { - FileLog.e("Proxy - can't get I/O streams!"); - FileLog.e(e.getMessage(), e); - return false; - } - } - - public void processRelay() { - try { - byte SOCKS_Version = getByteFromClient(); - - switch (SOCKS_Version) { - case SocksConstants.SOCKS4_Version: - comm = new Socks4Impl(this); - break; - case SocksConstants.SOCKS5_Version: - comm = new Socks5Impl(this); - break; - default: - FileLog.e("Invalid SOKCS version : " + SOCKS_Version); - return; - } - FileLog.d("Accepted SOCKS " + SOCKS_Version + " Request."); - - comm.authenticate(SOCKS_Version); - comm.getClientCommand(); - - if (comm.socksCommand == SocksConstants.SC_CONNECT) { - comm.connect(); - relay(); - } - } catch (Exception e) { - FileLog.e(e.getMessage(), e); - } - } - - public byte getByteFromClient() throws Exception { - while (m_ClientSocket != null) { - int b; - try { - b = m_ClientInput.read(); - } catch (InterruptedIOException e) { - Thread.yield(); - continue; - } - return (byte) b; // return loaded byte - } - throw new Exception("Interrupted Reading GetByteFromClient()"); - } - - public void relay() { - for (boolean isActive = true; isActive; Thread.yield()) { - int dlen = this.checkClientData(); - if (dlen < 0) { - isActive = false; - } - - if (dlen > 0) { - while (m_ServerSocket == null && error == null) { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - return; - } - } - if (error != null) throw new RuntimeException(error); - FileLog.d("[" + m_ServerSocket.request().url() + "] Send " + dlen + " bytes"); - this.m_ServerSocket.send(ByteString.of(Arrays.copyOf(this.m_Buffer, dlen))); - } - } - - } - - public int checkClientData() { - synchronized (m_lock) { - // The client side is not opened. - if (m_ClientInput == null) return -1; - - int dlen; - - try { - dlen = m_ClientInput.read(m_Buffer, 0, SocksConstants.DEFAULT_BUF_SIZE); - } catch (InterruptedIOException e) { - return 0; - } catch (IOException e) { - FileLog.d("Client connection Closed!"); - close(); // Close the server on this exception - return -1; - } - - if (dlen < 0) close(); - - return dlen; - } - } - -} diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/Socks4Impl.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/Socks4Impl.java deleted file mode 100644 index 45f20684f..000000000 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/Socks4Impl.java +++ /dev/null @@ -1,162 +0,0 @@ -package tw.nekomimi.nekogram.proxy.tcp2ws; - -import org.jetbrains.annotations.NotNull; -import org.telegram.messenger.FileLog; - -import java.net.InetAddress; - -public class Socks4Impl { - - final ProxyHandler m_Parent; - final byte[] DST_Port = new byte[2]; - byte[] DST_Addr = new byte[4]; - byte SOCKS_Version = 0; - byte socksCommand; - - InetAddress m_ServerIP = null; - int m_nServerPort = 0; - InetAddress m_ClientIP = null; - int m_nClientPort = 0; - - Socks4Impl(ProxyHandler Parent) { - m_Parent = Parent; - } - - public byte getSuccessCode() { - return 90; - } - - public byte getFailCode() { - return 91; - } - - @NotNull - public String commName(byte code) { - switch (code) { - case 0x01: - return "CONNECT"; - case 0x02: - return "BIND"; - case 0x03: - return "UDP Association"; - default: - return "Unknown Command"; - } - } - - @NotNull - public String replyName(byte code) { - switch (code) { - case 0: - return "SUCCESS"; - case 1: - return "General SOCKS Server failure"; - case 2: - return "Connection not allowed by ruleset"; - case 3: - return "Network Unreachable"; - case 4: - return "HOST Unreachable"; - case 5: - return "Connection Refused"; - case 6: - return "TTL Expired"; - case 7: - return "Command not supported"; - case 8: - return "Address Type not Supported"; - case 9: - return "to 0xFF UnAssigned"; - case 90: - return "Request GRANTED"; - case 91: - return "Request REJECTED or FAILED"; - case 92: - return "Request REJECTED - SOCKS server can't connect to Identd on the client"; - case 93: - return "Request REJECTED - Client and Identd report diff user-ID"; - default: - return "Unknown Command"; - } - } - - public boolean isInvalidAddress() { - // IP v4 Address Type - m_ServerIP = Utils.calcInetAddress(DST_Addr); - m_nServerPort = Utils.calcPort(DST_Port[0], DST_Port[1]); - - m_ClientIP = m_Parent.m_ClientSocket.getInetAddress(); - m_nClientPort = m_Parent.m_ClientSocket.getPort(); - - return m_ServerIP == null || m_nServerPort < 0; - } - - protected byte getByte() { - try { - return m_Parent.getByteFromClient(); - } catch (Exception e) { - return 0; - } - } - - public void authenticate(byte SOCKS_Ver) throws Exception { - SOCKS_Version = SOCKS_Ver; - } - - public void getClientCommand() throws Exception { - // Version was get in method Authenticate() - socksCommand = getByte(); - - DST_Port[0] = getByte(); - DST_Port[1] = getByte(); - - for (int i = 0; i < 4; i++) { - DST_Addr[i] = getByte(); - } - - //noinspection StatementWithEmptyBody - while (getByte() != 0x00) { - // keep reading bytes - } - - if ((socksCommand < SocksConstants.SC_CONNECT) || (socksCommand > SocksConstants.SC_BIND)) { - refuseCommand((byte) 91); - throw new Exception("Socks 4 - Unsupported Command : " + commName(socksCommand)); - } - - if (isInvalidAddress()) { // Gets the IP Address - refuseCommand((byte) 92); // Host Not Exists... - throw new Exception("Socks 4 - Unknown Host/IP address '" + m_ServerIP.toString()); - } - - FileLog.d("Accepted SOCKS 4 Command: \"" + commName(socksCommand) + "\""); - } - - public void replyCommand(byte ReplyCode) { - FileLog.d("Socks 4 reply: \"" + replyName(ReplyCode) + "\""); - - byte[] REPLY = new byte[8]; - REPLY[0] = 0; - REPLY[1] = ReplyCode; - REPLY[2] = DST_Port[0]; - REPLY[3] = DST_Port[1]; - REPLY[4] = DST_Addr[0]; - REPLY[5] = DST_Addr[1]; - REPLY[6] = DST_Addr[2]; - REPLY[7] = DST_Addr[3]; - - m_Parent.sendToClient(REPLY); - } - - protected void refuseCommand(byte errorCode) { - FileLog.d("Socks 4 - Refuse Command: \"" + replyName(errorCode) + "\""); - replyCommand(errorCode); - } - - public void connect() throws Exception { - FileLog.d("Connecting..."); - // Connect to the Remote Host - m_Parent.connectToServer(m_ServerIP.getHostAddress(), () -> replyCommand(getSuccessCode()), () -> refuseCommand(getFailCode())); - } - -} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/Socks5Impl.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/Socks5Impl.java deleted file mode 100644 index 4696b5c5e..000000000 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/Socks5Impl.java +++ /dev/null @@ -1,217 +0,0 @@ -package tw.nekomimi.nekogram.proxy.tcp2ws; - -import androidx.annotation.Nullable; - -import org.telegram.messenger.FileLog; - -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.net.UnknownHostException; - -public class Socks5Impl extends Socks4Impl { - - private static final int[] ADDR_Size = { - -1, //'00' No such AType - 4, //'01' IP v4 - 4Bytes - -1, //'02' No such AType - -1, //'03' First Byte is Len - 16 //'04' IP v6 - 16bytes - }; - private static final byte[] SRE_REFUSE = {(byte) 0x05, (byte) 0xFF}; - private static final byte[] SRE_ACCEPT = {(byte) 0x05, (byte) 0x00}; - private static final int MAX_ADDR_LEN = 255; - private byte ADDRESS_TYPE; - private DatagramSocket DGSocket = null; - private DatagramPacket DGPack = null; - private InetAddress UDP_IA = null; - private int UDP_port = 0; - - Socks5Impl(ProxyHandler Parent) { - super(Parent); - DST_Addr = new byte[MAX_ADDR_LEN]; - } - - @SuppressWarnings("OctalInteger") - public byte getSuccessCode() { - return 00; - } - - @SuppressWarnings("OctalInteger") - public byte getFailCode() { - return 04; - } - - @Nullable - public InetAddress calcInetAddress(byte AType, byte[] addr) { - InetAddress IA; - - switch (AType) { - // Version IP 4 - case 0x01: - IA = Utils.calcInetAddress(addr); - break; - // Version IP DOMAIN NAME - case 0x03: - if (addr[0] <= 0) { - FileLog.e("SOCKS 5 - calcInetAddress() : BAD IP in command - size : " + addr[0]); - return null; - } - StringBuilder sIA = new StringBuilder(); - for (int i = 1; i <= addr[0]; i++) { - sIA.append((char) addr[i]); - } - try { - IA = InetAddress.getByName(sIA.toString()); - } catch (UnknownHostException e) { - return null; - } - break; - default: - return null; - } - return IA; - } - - public boolean isInvalidAddress() { - m_ServerIP = calcInetAddress(ADDRESS_TYPE, DST_Addr); - m_nServerPort = Utils.calcPort(DST_Port[0], DST_Port[1]); - - m_ClientIP = m_Parent.m_ClientSocket.getInetAddress(); - m_nClientPort = m_Parent.m_ClientSocket.getPort(); - - return !((m_ServerIP != null) && (m_nServerPort >= 0)); - } - - public void authenticate(byte SOCKS_Ver) throws Exception { - super.authenticate(SOCKS_Ver); // Sets SOCKS Version... - - if (SOCKS_Version == SocksConstants.SOCKS5_Version) { - if (!checkAuthentication()) {// It reads whole Cli Request - refuseAuthentication("SOCKS 5 - Not Supported Authentication!"); - throw new Exception("SOCKS 5 - Not Supported Authentication."); - } - acceptAuthentication(); - }// if( SOCKS_Version... - else { - refuseAuthentication("Incorrect SOCKS version : " + SOCKS_Version); - throw new Exception("Not Supported SOCKS Version -'" + - SOCKS_Version + "'"); - } - } - - public void refuseAuthentication(String msg) { - FileLog.d("SOCKS 5 - Refuse Authentication: '" + msg + "'"); - m_Parent.sendToClient(SRE_REFUSE); - } - - - public void acceptAuthentication() { - FileLog.d("SOCKS 5 - Accepts Auth. method 'NO_AUTH'"); - byte[] tSRE_Accept = SRE_ACCEPT; - tSRE_Accept[0] = SOCKS_Version; - m_Parent.sendToClient(tSRE_Accept); - } - - - public boolean checkAuthentication() { - final byte Methods_Num = getByte(); - final StringBuilder Methods = new StringBuilder(); - - for (int i = 0; i < Methods_Num; i++) { - Methods.append(",-").append(getByte()).append('-'); - } - - return ((Methods.indexOf("-0-") != -1) || (Methods.indexOf("-00-") != -1)); - } - - public void getClientCommand() throws Exception { - SOCKS_Version = getByte(); - socksCommand = getByte(); - /*byte RSV =*/ - getByte(); // Reserved. Must be'00' - ADDRESS_TYPE = getByte(); - - int Addr_Len = ADDR_Size[ADDRESS_TYPE]; - DST_Addr[0] = getByte(); - if (ADDRESS_TYPE == 0x03) { - Addr_Len = DST_Addr[0] + 1; - } - - for (int i = 1; i < Addr_Len; i++) { - DST_Addr[i] = getByte(); - } - DST_Port[0] = getByte(); - DST_Port[1] = getByte(); - - if (SOCKS_Version != SocksConstants.SOCKS5_Version) { - FileLog.d("SOCKS 5 - Incorrect SOCKS Version of Command: " + - SOCKS_Version); - refuseCommand((byte) 0xFF); - throw new Exception("Incorrect SOCKS Version of Command: " + - SOCKS_Version); - } - - if ((socksCommand < SocksConstants.SC_CONNECT) || (socksCommand > SocksConstants.SC_UDP)) { - FileLog.e("SOCKS 5 - GetClientCommand() - Unsupported Command : \"" + commName(socksCommand) + "\""); - refuseCommand((byte) 0x07); - throw new Exception("SOCKS 5 - Unsupported Command: \"" + socksCommand + "\""); - } - - if (ADDRESS_TYPE == 0x04) { - FileLog.e("SOCKS 5 - GetClientCommand() - Unsupported Address Type - IP v6"); - refuseCommand((byte) 0x08); - throw new Exception("Unsupported Address Type - IP v6"); - } - - if ((ADDRESS_TYPE >= 0x04) || (ADDRESS_TYPE <= 0)) { - FileLog.e("SOCKS 5 - GetClientCommand() - Unsupported Address Type: " + ADDRESS_TYPE); - refuseCommand((byte) 0x08); - throw new Exception("SOCKS 5 - Unsupported Address Type: " + ADDRESS_TYPE); - } - - if (isInvalidAddress()) { // Gets the IP Address - refuseCommand((byte) 0x04); // Host Not Exists... - throw new Exception("SOCKS 5 - Unknown Host/IP address '" + m_ServerIP.toString() + "'"); - } - - FileLog.d("SOCKS 5 - Accepted SOCKS5 Command: \"" + commName(socksCommand) + "\""); - } - - public void replyCommand(byte replyCode) { - FileLog.d("SOCKS 5 - Reply to Client \"" + replyName(replyCode) + "\""); - - final int pt; - - byte[] REPLY = new byte[10]; - byte[] IP = new byte[4]; - - if (m_Parent.m_ServerSocketRaw != null) { - pt = m_Parent.m_ServerSocketRaw.getLocalPort(); - } else { - IP[0] = 0; - IP[1] = 0; - IP[2] = 0; - IP[3] = 0; - pt = 0; - } - - formGenericReply(replyCode, pt, REPLY, IP); - - m_Parent.sendToClient(REPLY);// BND.PORT - } - - private void formGenericReply(byte replyCode, int pt, byte[] REPLY, byte[] IP) { - REPLY[0] = SocksConstants.SOCKS5_Version; - REPLY[1] = replyCode; - REPLY[2] = 0x00; // Reserved '00' - REPLY[3] = 0x01; // DOMAIN NAME Address Type IP v4 - REPLY[4] = IP[0]; - REPLY[5] = IP[1]; - REPLY[6] = IP[2]; - REPLY[7] = IP[3]; - REPLY[8] = (byte) ((pt & 0xFF00) >> 8);// Port High - REPLY[9] = (byte) (pt & 0x00FF); // Port Low - } - -} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/SocksConstants.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/SocksConstants.java deleted file mode 100644 index 89b48d016..000000000 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/SocksConstants.java +++ /dev/null @@ -1,18 +0,0 @@ -package tw.nekomimi.nekogram.proxy.tcp2ws; - -public interface SocksConstants { - - // refactor - int LISTEN_TIMEOUT = 2000; - int DEFAULT_SERVER_TIMEOUT = 2000; - - int DEFAULT_BUF_SIZE = 4096; - int DEFAULT_PROXY_TIMEOUT = 2000; - - byte SOCKS5_Version = 0x05; - byte SOCKS4_Version = 0x04; - - byte SC_CONNECT = 0x01; - byte SC_BIND = 0x02; - byte SC_UDP = 0x03; -} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/Tcp2wsServer.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/Tcp2wsServer.java index 41ea48e8f..938e05b1c 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/Tcp2wsServer.java +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/Tcp2wsServer.java @@ -6,54 +6,52 @@ import java.io.IOException; import java.io.InterruptedIOException; import java.net.ServerSocket; import java.net.Socket; +import java.util.Collections; import java.util.HashMap; - -import tw.nekomimi.nekogram.proxy.WsLoader; +import java.util.Map; public class Tcp2wsServer extends Thread { - public WsLoader.Bean bean; - public int port; + public final WsLoader.Bean bean; + public final int port; public Tcp2wsServer(WsLoader.Bean bean, int port) { this.bean = bean; this.port = port; } - public static final HashMap mapper = new HashMap<>(); - - static { - mapper.put("149.154.175.5", 1); - mapper.put("95.161.76.100", 2); - mapper.put("149.154.175.100", 3); - mapper.put("149.154.167.91", 4); - mapper.put("149.154.167.92", 4); - mapper.put("149.154.171.5", 5); - mapper.put("2001:b28:f23d:f001:0000:0000:0000:000a", 1); - mapper.put("2001:67c:4e8:f002:0000:0000:0000:000a", 2); - mapper.put("2001:b28:f23d:f003:0000:0000:0000:000a", 3); - mapper.put("2001:67c:4e8:f004:0000:0000:0000:000a", 4); - mapper.put("2001:b28:f23f:f005:0000:0000:0000:000a", 5); - mapper.put("149.154.161.144", 2); - mapper.put("149.154.167.", 2); - mapper.put("149.154.175.1", 3); - mapper.put("91.108.4.", 4); - mapper.put("149.154.164.", 4); - mapper.put("149.154.165.", 4); - mapper.put("149.154.166.", 4); - mapper.put("91.108.56.", 5); - mapper.put("2001:b28:f23d:f001:0000:0000:0000:000d", 1); - mapper.put("2001:67c:4e8:f002:0000:0000:0000:000d", 2); - mapper.put("2001:b28:f23d:f003:0000:0000:0000:000d", 3); - mapper.put("2001:67c:4e8:f004:0000:0000:0000:000d", 4); - mapper.put("2001:b28:f23f:f005:0000:0000:0000:000d", 5); - mapper.put("149.154.175.40", 6); - mapper.put("149.154.167.40", 7); - mapper.put("149.154.175.117", 8); - mapper.put("2001:b28:f23d:f001:0000:0000:0000:000e", 6); - mapper.put("2001:67c:4e8:f002:0000:0000:0000:000e", 7); - mapper.put("2001:b28:f23d:f003:0000:0000:0000:000e", 8); - } + public static final Map mapper = Collections.unmodifiableMap(new HashMap() {{ + put("149.154.175.5", 1); + put("95.161.76.100", 2); + put("149.154.175.100", 3); + put("149.154.167.91", 4); + put("149.154.167.92", 4); + put("149.154.171.5", 5); + put("2001:b28:f23d:f001:0000:0000:0000:000a", 1); + put("2001:67c:4e8:f002:0000:0000:0000:000a", 2); + put("2001:b28:f23d:f003:0000:0000:0000:000a", 3); + put("2001:67c:4e8:f004:0000:0000:0000:000a", 4); + put("2001:b28:f23f:f005:0000:0000:0000:000a", 5); + put("149.154.161.144", 2); + put("149.154.167.", 2); + put("149.154.175.1", 3); + put("91.108.4.", 4); + put("149.154.164.", 4); + put("149.154.165.", 4); + put("149.154.166.", 4); + put("91.108.56.", 5); + put("2001:b28:f23d:f001:0000:0000:0000:000d", 1); + put("2001:67c:4e8:f002:0000:0000:0000:000d", 2); + put("2001:b28:f23d:f003:0000:0000:0000:000d", 3); + put("2001:67c:4e8:f004:0000:0000:0000:000d", 4); + put("2001:b28:f23f:f005:0000:0000:0000:000d", 5); + put("149.154.175.40", 6); + put("149.154.167.40", 7); + put("149.154.175.117", 8); + put("2001:b28:f23d:f001:0000:0000:0000:000e", 6); + put("2001:67c:4e8:f002:0000:0000:0000:000e", 7); + put("2001:b28:f23d:f003:0000:0000:0000:000e", 8); + }}); @Override public void run() { @@ -61,39 +59,33 @@ public class Tcp2wsServer extends Thread { try { handleClients(port); FileLog.d("SOCKS server stopped..."); - } catch (IOException e) { + } catch (Exception e) { FileLog.d("SOCKS server crashed..."); + FileLog.e(e); interrupt(); } } - protected void handleClients(int port) throws IOException { + protected void handleClients(int port) throws Exception { final ServerSocket listenSocket = new ServerSocket(port); - listenSocket.setSoTimeout(SocksConstants.LISTEN_TIMEOUT); - Tcp2wsServer.this.port = listenSocket.getLocalPort(); + listenSocket.setSoTimeout(2000); FileLog.d("SOCKS server listening at port: " + listenSocket.getLocalPort()); while (isAlive() && !isInterrupted()) { - handleNextClient(listenSocket); + try { + final Socket clientSocket = listenSocket.accept(); + FileLog.d("Connection from : " + clientSocket.getRemoteSocketAddress().toString()); + new WsProxyHandler(clientSocket, bean).start(); + } catch (InterruptedIOException e) { + // This exception is thrown when accept timeout is expired + } catch (Exception e) { + FileLog.e(e.getMessage(), e); + } } - try { listenSocket.close(); } catch (IOException e) { - // ignore - } - } - - private void handleNextClient(ServerSocket listenSocket) { - try { - final Socket clientSocket = listenSocket.accept(); - clientSocket.setSoTimeout(SocksConstants.DEFAULT_SERVER_TIMEOUT); - FileLog.d("Connection from : " + Utils.getSocketInfo(clientSocket)); - new Thread(new ProxyHandler(clientSocket, mapper, bean)).start(); - } catch (InterruptedIOException e) { - // This exception is thrown when accept timeout is expired - } catch (Exception e) { - FileLog.e(e.getMessage(), e); + FileLog.e(e); } } } \ No newline at end of file diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/Utils.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/Utils.java deleted file mode 100644 index 8658e10e7..000000000 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/Utils.java +++ /dev/null @@ -1,72 +0,0 @@ -package tw.nekomimi.nekogram.proxy.tcp2ws; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.DatagramPacket; -import java.net.InetAddress; -import java.net.Socket; -import java.net.UnknownHostException; - -import static java.lang.String.format; - -public final class Utils { - - private static final Logger LOGGER = LoggerFactory.getLogger(Utils.class); - - @Nullable - public static InetAddress calcInetAddress(byte[] addr) { - InetAddress IA; - StringBuilder sIA = new StringBuilder(); - - if (addr.length < 4) { - LOGGER.error("calcInetAddress() - Invalid length of IP v4 - " + addr.length + " bytes"); - return null; - } - - // IP v4 Address Type - for (int i = 0; i < 4; i++) { - sIA.append(byte2int(addr[i])); - if (i < 3) sIA.append("."); - } - - try { - IA = InetAddress.getByName(sIA.toString()); - } catch (UnknownHostException e) { - return null; - } - - return IA; - } - - public static int byte2int(byte b) { - return (int) b < 0 ? 0x100 + (int) b : b; - } - - public static int calcPort(byte Hi, byte Lo) { - return ((byte2int(Hi) << 8) | byte2int(Lo)); - } - - @NotNull - public static String iP2Str(InetAddress IP) { - return IP == null - ? "NA/NA" - : format("%s/%s", IP.getHostName(), IP.getHostAddress()); - } - - @NotNull - public static String getSocketInfo(Socket sock) { - return sock == null - ? "" - : format("<%s:%d>", Utils.iP2Str(sock.getInetAddress()), sock.getPort()); - } - - @NotNull - public static String getSocketInfo(DatagramPacket DGP) { - return DGP == null - ? "" - : format("<%s:%d>", Utils.iP2Str(DGP.getAddress()), DGP.getPort()); - } -} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/WsLoader.kt b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/WsLoader.kt similarity index 75% rename from TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/WsLoader.kt rename to TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/WsLoader.kt index 2a5249d19..1503ea648 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/WsLoader.kt +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/WsLoader.kt @@ -1,11 +1,10 @@ -package tw.nekomimi.nekogram.proxy +package tw.nekomimi.nekogram.proxy.tcp2ws import cn.hutool.core.codec.Base64 import cn.hutool.core.util.StrUtil import okhttp3.Dns import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import tw.nekomimi.nekogram.proxy.tcp2ws.Tcp2wsServer import tw.nekomimi.nekogram.NekoConfig import java.net.InetAddress @@ -71,21 +70,4 @@ class WsLoader { } } - // For OKHttp in ProxyHandler.java - class CustomDns : Dns { - override fun lookup(hostname: String): List { - val list = ArrayList() - val ip = NekoConfig.customPublicProxyIP.String() - if (StrUtil.isBlank(ip)) { - return Dns.SYSTEM.lookup(hostname) - } - return try { - list.add(InetAddress.getByName(ip)) - list - } catch (e: Exception) { - Dns.SYSTEM.lookup(hostname) - } - } - } - } \ No newline at end of file diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/WsProxyHandler.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/WsProxyHandler.java new file mode 100644 index 000000000..57f82f75d --- /dev/null +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/proxy/tcp2ws/WsProxyHandler.java @@ -0,0 +1,287 @@ +package tw.nekomimi.nekogram.proxy.tcp2ws; + +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.telegram.messenger.FileLog; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; +import okio.ByteString; +import tw.nekomimi.nekogram.NekoConfig; + +public class WsProxyHandler extends Thread { + + private InputStream clientInputStream = null; + private OutputStream clientOutputStream = null; + + private final WsLoader.Bean bean; + private Socket clientSocket; + private WebSocket wsSocket = null; + private final byte[] buffer = new byte[4096]; + + private final AtomicInteger wsStatus = new AtomicInteger(0); + private final static int STATUS_OPENED = 1; + private final static int STATUS_CLOSED = 2; + private final static int STATUS_FAILED = 3; + + public WsProxyHandler(Socket clientSocket, WsLoader.Bean bean) { + this.bean = bean; + this.clientSocket = clientSocket; + FileLog.d("ProxyHandler Created."); + } + + @Override + public void run() { + FileLog.d("Proxy Started."); + try { + clientInputStream = clientSocket.getInputStream(); + clientOutputStream = clientSocket.getOutputStream(); + // Handle Socks5 HandShake + socks5Handshake(); + FileLog.d("socks5 handshake and websocket connection done"); + // Start read from client socket and send to websocket + while (clientSocket != null && wsSocket != null && wsStatus.get() == STATUS_OPENED && !clientSocket.isClosed() && !clientSocket.isInputShutdown()) { + int readLen = this.clientInputStream.read(buffer); + FileLog.d(String.format("read %d from client", readLen)); + if (readLen == -1) { + close(); + return; + } + if (readLen < 10) { + FileLog.d(Arrays.toString(Arrays.copyOf(buffer, readLen))); + } + this.wsSocket.send(ByteString.of(Arrays.copyOf(buffer, readLen))); + } + } catch (SocketException se) { + if ("Socket closed".equals(se.getMessage())) { + FileLog.d("socket closed from ws when reading from client"); + close(); + } else { + FileLog.e(se); + close(); + } + } + catch (Exception e) { + FileLog.e(e); + close(); + } + } + + public void close() { + int cur = wsStatus.get(); + if (cur == STATUS_CLOSED) + return; + wsStatus.set(STATUS_CLOSED); + FileLog.d("ws handler closed"); + + try { + if (clientSocket != null) + clientSocket.close(); + } catch (IOException e) { + // ignore + } + try { + if (wsSocket != null) { + wsSocket.close(1000, ""); + } + } catch (Exception e) { + // ignore + } + + clientSocket = null; + wsSocket = null; + } + + private static OkHttpClient okhttpClient = null; + private static final Object okhttpLock = new Object(); + + private static OkHttpClient getOkHttpClientInstance() { + if (okhttpClient == null) { + synchronized (okhttpLock) { + if (okhttpClient == null) { + okhttpClient = new OkHttpClient.Builder() + .dns(s -> { + ArrayList ret = new ArrayList<>(); + FileLog.d("okhttpWS: resolving: " + s); + if (StringUtils.isNotBlank(NekoConfig.customPublicProxyIP.String())) { + ret.add(InetAddress.getByName(NekoConfig.customPublicProxyIP.String())); + } else + ret.addAll(Arrays.asList(InetAddress.getAllByName(s))); + FileLog.d("okhttpWS: resolved: " + ret.toString()); + return ret; + }) + .build(); + } + } + } + return okhttpClient; + } + + private void connectToServer(String wsHost) { + getOkHttpClientInstance() + .newWebSocket(new Request.Builder() + .url((bean.getTls() ? "wss://" : "ws://") + wsHost + "/api") + .build(), new WebSocketListener() { + @Override + public void onOpen(@NotNull okhttp3.WebSocket webSocket, @NotNull Response response) { + WsProxyHandler.this.wsSocket = webSocket; + wsStatus.set(STATUS_OPENED); + synchronized (wsStatus) { + wsStatus.notify(); + } + } + + @Override + public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable t, @Nullable Response response) { + FileLog.e(t); + wsStatus.set(STATUS_FAILED); + synchronized (wsStatus) { + wsStatus.notify(); + } + WsProxyHandler.this.close(); + } + + @Override + public void onMessage(@NotNull okhttp3.WebSocket webSocket, @NotNull ByteString bytes) { + FileLog.d("[" + wsHost + "] Received " + bytes.size() + " bytes"); + try { + if (wsStatus.get() == STATUS_OPENED && !WsProxyHandler.this.clientSocket.isOutputShutdown()) + WsProxyHandler.this.clientOutputStream.write(bytes.toByteArray()); + } catch (IOException e) { + FileLog.e(e); + WsProxyHandler.this.close(); + } + } + + @Override + public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) { + FileLog.d("[" + wsHost + "] Received text: " + text); + } + + @Override + public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) { + FileLog.d("[" + wsHost + "] Closed: " + code + " " + reason); + WsProxyHandler.this.close(); + synchronized (wsStatus) { + wsStatus.notify(); + } + } + + @Override + public void onClosing(@NotNull WebSocket webSocket, int code, @NotNull String reason) { + FileLog.d("[" + wsHost + "] Closing: " + code + " " + reason); + WsProxyHandler.this.close(); + synchronized (wsStatus) { + wsStatus.notify(); + } + } + }); + } + + private static final byte[] RESP_AUTH = new byte[]{0x05, 0x00}; + private static final byte[] RESP_SUCCESS = new byte[]{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + private static final byte[] RESP_FAILED = new byte[]{0x05, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + private void socks5Handshake() throws Exception { + byte socksVersion = readOneByteFromClient(); + + if (socksVersion != 0x05) { + throw new Exception("Invalid socks version:" + socksVersion); + } + FileLog.d("Accepted socks5 requests."); + + byte authMethodsLen = readOneByteFromClient(); + boolean isNoAuthSupport = false; + for (int i = 0; i < authMethodsLen; i++) { + byte authMethod = readOneByteFromClient(); + if (authMethod == 0x00) + isNoAuthSupport = true; + } + if (!isNoAuthSupport) throw new Exception("NO_AUTH is not supported from client."); + + this.clientOutputStream.write(RESP_AUTH); + this.clientOutputStream.flush(); + + byte[] cmds = readBytesExactly(4); + // cmds[0] -> VER + // cmds[1] -> CMD + // cmds[2] -> RSV + // cmds[3] -> ADDR_TYPE + if (cmds[0] != 0x05 || cmds[1] != 0x01 || cmds[2] != 0x00) + throw new Exception("invalid socks5 cmds " + Arrays.toString(cmds)); + int addrType = cmds[3]; + String address; + if (addrType == 0x01) { // ipv4 + address = InetAddress.getByAddress(readBytesExactly(4)).getHostAddress(); + } else if (addrType == 0x04) { // ipv6 + address = Inet6Address.getByAddress(readBytesExactly(16)).getHostAddress(); + } else { // not supported: domain + throw new Exception("invalid addr type: " + addrType); + } + readBytesExactly(2); // read out port + + String wsHost = getWsHost(address); + connectToServer(wsHost); + + synchronized (wsStatus) { + wsStatus.wait(); + } + + if (wsStatus.get() == STATUS_OPENED) { + this.clientOutputStream.write(RESP_SUCCESS); + this.clientOutputStream.flush(); + } else { + this.clientOutputStream.write(RESP_FAILED); + this.clientOutputStream.flush(); + throw new Exception("websocket connect failed"); + } + // just set status byte and ignore bnd.addr and bnd.port in RFC1928, since Telegram Android ignores it: + // proxyAuthState == 6 in tgnet/ConnectionSocket.cpp + } + + private String getWsHost(String address) throws Exception { + Integer dcNumber = Tcp2wsServer.mapper.get(address); + for (int i = 1; dcNumber == null && i < 4; i++) { + dcNumber = Tcp2wsServer.mapper.get(address.substring(0, address.length() - i)); + } + if (dcNumber == null) + throw new Exception("no matched dc: " + address); + if (dcNumber >= bean.getPayload().size()) + throw new Exception("invalid dc number & payload: " + dcNumber); + String serverPrefix = bean.getPayload().get(dcNumber - 1); + String wsHost = serverPrefix + "." + this.bean.getServer(); + FileLog.d("socks5 dest address: " + address + ", target ws host " + wsHost); + return wsHost; + } + + private byte readOneByteFromClient() throws Exception { + return (byte) clientInputStream.read(); + } + + private byte[] readBytesExactly(int len) throws Exception { + byte[] ret = new byte[len]; + int alreadyRead = 0; + while (alreadyRead < len) { + int read = this.clientInputStream.read(ret, alreadyRead, len - alreadyRead); + alreadyRead += read; + } + return ret; + } + +}