mirror of
https://github.com/NekoX-Dev/NekoX.git
synced 2024-12-12 10:10:07 +01:00
refactor ws relay
This commit is contained in:
parent
e44072e04b
commit
58d91d99aa
@ -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;
|
||||
|
@ -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));
|
||||
|
||||
|
@ -20,20 +20,16 @@ fun loadProxiesPublic(urls: List<String>, exceptions: MutableMap<String, Excepti
|
||||
return emptyList()
|
||||
// Try DoH first ( github.com is often blocked
|
||||
try {
|
||||
var content = DnsFactory.getTxts("nachonekodayo.sekai.icu").joinToString()
|
||||
val content = DnsFactory.getTxts("nachonekodayo.sekai.icu").joinToString()
|
||||
|
||||
val proxiesString = StrUtil.getSubString(content, "#NekoXStart#", "#NekoXEnd#")
|
||||
if (proxiesString.equals(content)) {
|
||||
throw Exception("DoH get public proxy: Not found")
|
||||
}
|
||||
|
||||
val proxies = parseProxies(proxiesString)
|
||||
if (proxies.count() == 0) {
|
||||
throw Exception("DoH get public proxy: Empty")
|
||||
}
|
||||
return proxies
|
||||
return parseProxies(proxiesString)
|
||||
} catch (e: Exception) {
|
||||
FileLog.e(e.stackTraceToString())
|
||||
FileLog.e(e)
|
||||
}
|
||||
|
||||
// Try Other Urls
|
||||
|
@ -43,6 +43,7 @@ import org.telegram.ui.Components.LayoutHelper;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import tw.nekomimi.nekogram.proxy.tcp2ws.WsLoader;
|
||||
|
||||
public class WsSettingsActivity extends BaseFragment {
|
||||
|
||||
|
@ -1,300 +0,0 @@
|
||||
package tw.nekomimi.nekogram.proxy.tcp2ws;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.telegram.messenger.BuildConfig;
|
||||
import org.telegram.messenger.FileLog;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
import okhttp3.WebSocketListener;
|
||||
import okhttp3.internal.NativeImageTestsAccessorsKt;
|
||||
import okio.ByteString;
|
||||
import tw.nekomimi.nekogram.proxy.WsLoader;
|
||||
|
||||
public class ProxyHandler implements Runnable {
|
||||
|
||||
private InputStream m_ClientInput = null;
|
||||
private OutputStream m_ClientOutput = null;
|
||||
private Object m_lock;
|
||||
private Socks4Impl comm = null;
|
||||
|
||||
Socket m_ClientSocket;
|
||||
WebSocket m_ServerSocket = null;
|
||||
Socket m_ServerSocketRaw = null;
|
||||
Throwable error = null;
|
||||
HashMap<String, Integer> mapper;
|
||||
WsLoader.Bean bean;
|
||||
byte[] m_Buffer = new byte[SocksConstants.DEFAULT_BUF_SIZE];
|
||||
|
||||
public ProxyHandler(Socket clientSocket, HashMap<String, Integer> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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()));
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -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<String, Integer> 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<String, Integer> mapper = Collections.unmodifiableMap(new HashMap<String, Integer>() {{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
? "<NA/NA:0>"
|
||||
: format("<%s:%d>", Utils.iP2Str(sock.getInetAddress()), sock.getPort());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getSocketInfo(DatagramPacket DGP) {
|
||||
return DGP == null
|
||||
? "<NA/NA:0>"
|
||||
: format("<%s:%d>", Utils.iP2Str(DGP.getAddress()), DGP.getPort());
|
||||
}
|
||||
}
|
@ -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<InetAddress> {
|
||||
val list = ArrayList<InetAddress>()
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<InetAddress> 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;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user