Mult Port + TCP Calibration (SSH DEAD)
This commit is contained in:
@@ -18,3 +18,10 @@ Apache 2.0 license is included at `licenses/FreeProxy-APACHE-2.0.txt`.
|
||||
|
||||
The rest of the DragonTCP-specific glue, UI, Go transport, and server code in
|
||||
this bundle is provided as part of this generated project.
|
||||
|
||||
## golang.org/x/crypto
|
||||
|
||||
The embedded DragonTCP Go client uses **golang.org/x/crypto** for the SSH
|
||||
protocol implementation. It is distributed under the Go project's BSD-style
|
||||
license. The license text is included in this APK as
|
||||
`golang-x-crypto-BSD-3-Clause.txt`.
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -34,14 +34,16 @@ public class DragonService extends VpnService {
|
||||
public static final String ACTION_STATE = "com.dragontcp.client.STATE";
|
||||
public static final String EXTRA_ACTIVE = "active";
|
||||
public static final String EXTRA_STATUS = "status";
|
||||
public static final String EXTRA_UP_CHUNK = "upChunk";
|
||||
public static final String EXTRA_DOWN_CHUNK = "downChunk";
|
||||
|
||||
public static final String EXTRA_SERVER = "server";
|
||||
public static final String EXTRA_PORT = "port";
|
||||
public static final String EXTRA_PORT_START = "portStart";
|
||||
public static final String EXTRA_PORT_END = "portEnd";
|
||||
public static final String EXTRA_WORKING_PORT = "workingPort";
|
||||
public static final String EXTRA_TOKEN = "token";
|
||||
/** Wire format: "auto", "b", "bp" or "x". */
|
||||
public static final String EXTRA_WIRE = "wire";
|
||||
public static final String EXTRA_CHUNK_MAX = "chunkMax";
|
||||
public static final String EXTRA_CHUNK_MIN = "chunkMin";
|
||||
/** Maximum download records per request. A transport setting, not a thread count. */
|
||||
public static final String EXTRA_BATCH_MAX = "batchMax";
|
||||
/** Minimum download records per request; equal to the maximum pins the depth. */
|
||||
@@ -52,17 +54,27 @@ public class DragonService extends VpnService {
|
||||
public static final String EXTRA_PROBE_THREADS = "probeThreads";
|
||||
|
||||
private static final int BATCH_LIMIT = 256;
|
||||
private static final int CHUNK_MIN = 32;
|
||||
private static final int CHUNK_MAX = 1024 * 1024;
|
||||
|
||||
private static final int NOTIFICATION_ID = 53;
|
||||
private static final String CHANNEL_ID = "dragontcp-lite";
|
||||
private static final int LOCAL_PROXY_PORT = 8080;
|
||||
private static final int TUN_MTU = 1400;
|
||||
private static final int RUNTIME_SHRINK_AFTER = 1;
|
||||
private static final int RUNTIME_SHRINK_STEP = 200;
|
||||
|
||||
private static volatile boolean appActive;
|
||||
private static volatile String appStatus = "DISCONNECTED";
|
||||
private static volatile int appUpChunk;
|
||||
private static volatile int appDownChunk;
|
||||
private static volatile int appWorkingPort;
|
||||
|
||||
public static boolean isActive() { return appActive; }
|
||||
public static String currentStatus() { return appStatus; }
|
||||
public static int currentUpChunk() { return appUpChunk; }
|
||||
public static int currentDownChunk() { return appDownChunk; }
|
||||
public static int currentWorkingPort() { return appWorkingPort; }
|
||||
|
||||
private final Object stateLock = new Object();
|
||||
private volatile Process coreProcess;
|
||||
@@ -96,6 +108,9 @@ public class DragonService extends VpnService {
|
||||
return START_STICKY;
|
||||
}
|
||||
cancelRequested = false;
|
||||
appUpChunk = 0;
|
||||
appDownChunk = 0;
|
||||
appWorkingPort = 0;
|
||||
publishState("CONNECTING", true);
|
||||
}
|
||||
startForeground(NOTIFICATION_ID, buildNotification("Starting..."));
|
||||
@@ -112,16 +127,17 @@ public class DragonService extends VpnService {
|
||||
}
|
||||
|
||||
String server = intent.getStringExtra(EXTRA_SERVER);
|
||||
int port = intent.getIntExtra(EXTRA_PORT, 53);
|
||||
int portStart = intent.getIntExtra(EXTRA_PORT_START, 53);
|
||||
int portEnd = intent.getIntExtra(EXTRA_PORT_END, portStart);
|
||||
String token = intent.getStringExtra(EXTRA_TOKEN);
|
||||
String wire = intent.getStringExtra(EXTRA_WIRE);
|
||||
int chunkMax = intent.getIntExtra(EXTRA_CHUNK_MAX, 1024 * 1024);
|
||||
int chunkMin = intent.getIntExtra(EXTRA_CHUNK_MIN, 32);
|
||||
int chunkMax = CHUNK_MAX;
|
||||
int chunkMin = CHUNK_MIN;
|
||||
int batchMax = intent.getIntExtra(EXTRA_BATCH_MAX, 1);
|
||||
int batchMin = intent.getIntExtra(EXTRA_BATCH_MIN, 1);
|
||||
int reconnect = intent.getIntExtra(EXTRA_RECONNECT, 1);
|
||||
int timeout = intent.getIntExtra(EXTRA_TIMEOUT, 5);
|
||||
int probeDelay = intent.getIntExtra(EXTRA_PROBE_DELAY, 1000);
|
||||
int probeDelay = intent.getIntExtra(EXTRA_PROBE_DELAY, 100);
|
||||
int probeThreads = intent.getIntExtra(EXTRA_PROBE_THREADS, 1);
|
||||
|
||||
if (server == null || server.trim().isEmpty()) {
|
||||
@@ -131,27 +147,36 @@ public class DragonService extends VpnService {
|
||||
server = server.trim();
|
||||
if (token == null) token = "";
|
||||
if (wire == null || !(wire.equals("b") || wire.equals("bp") || wire.equals("x"))) wire = "auto";
|
||||
chunkMax = Math.max(32, Math.min(1024 * 1024, chunkMax));
|
||||
chunkMin = Math.max(32, Math.min(chunkMax, chunkMin));
|
||||
batchMax = Math.max(1, Math.min(BATCH_LIMIT, batchMax));
|
||||
batchMin = Math.max(1, Math.min(batchMax, batchMin));
|
||||
reconnect = Math.max(0, reconnect);
|
||||
timeout = Math.max(1, timeout);
|
||||
probeDelay = Math.max(200, Math.min(30000, probeDelay));
|
||||
probeDelay = Math.max(50, Math.min(30000, probeDelay));
|
||||
probeThreads = Math.max(1, Math.min(16, probeThreads));
|
||||
|
||||
if (portStart < 1 || portStart > 65535 || portEnd < 1 || portEnd > 65535 || portStart > portEnd) {
|
||||
failStart("Invalid server port range");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
AppLog.append("Starting DragonTCP → " + server + ":" + port);
|
||||
AppLog.append("Starting DragonTCP → " + server + " ports " + portStart + "-" + portEnd);
|
||||
AppLog.append("Port scan: ascending " + portStart + "-" + portEnd + " • one candidate at a time • real DragonTCP validation");
|
||||
AppLog.append(describeBatch(batchMin, batchMax));
|
||||
AppLog.append(describeWire(wire));
|
||||
AppLog.append("Wire probe: http://ip.dr2.site/ • delay " + probeDelay + " ms • threads " + probeThreads);
|
||||
Process process = startDragonCore(server, port, token, chunkMax, chunkMin, batchMax, batchMin, wire, reconnect, timeout, probeDelay, probeThreads);
|
||||
AppLog.append("Payload: SHA-256 masked framing (clear-payload mode disabled in Android)");
|
||||
AppLog.append("Chunk startup: ASCENDING calibration from " + chunkMin + " to " + chunkMax
|
||||
+ " bytes; 4x growth then ~32-byte boundary refinement");
|
||||
AppLog.append("Runtime chunk recovery: reduce immediately by " + RUNTIME_SHRINK_STEP
|
||||
+ " bytes after a recoverable transfer failure; I/O timeout kills the connection instead");
|
||||
AppLog.append("Header scan: valid masks only • server-local probe • delay " + probeDelay + " ms • threads " + probeThreads);
|
||||
AppLog.append("SSH: disabled in Android app (server/core SSH support retained)");
|
||||
Process process = startDragonCore(server, portStart, portEnd, token, chunkMax, chunkMin, batchMax, batchMin, wire,
|
||||
reconnect, timeout, probeDelay, probeThreads);
|
||||
synchronized (stateLock) { coreProcess = process; }
|
||||
|
||||
startCoreLogReader(process);
|
||||
waitForLocalProxy(process);
|
||||
waitForLocalProxy(process, LOCAL_PROXY_PORT);
|
||||
if (cancelRequested) throw new InterruptedException("Stop requested");
|
||||
AppLog.append("Local DragonTCP proxy ready on 127.0.0.1:8080");
|
||||
AppLog.append("Local DragonTCP proxy ready on 127.0.0.1:" + LOCAL_PROXY_PORT);
|
||||
|
||||
ParcelFileDescriptor pfd = establishVpn();
|
||||
if (pfd == null) throw new IllegalStateException("Android refused to establish the VPN interface");
|
||||
@@ -205,7 +230,8 @@ public class DragonService extends VpnService {
|
||||
|
||||
private Process startDragonCore(
|
||||
String server,
|
||||
int port,
|
||||
int portStart,
|
||||
int portEnd,
|
||||
String token,
|
||||
int chunkMax,
|
||||
int chunkMin,
|
||||
@@ -225,7 +251,8 @@ public class DragonService extends VpnService {
|
||||
cmd.add("--listen-host"); cmd.add("127.0.0.1");
|
||||
cmd.add("--listen-port"); cmd.add(Integer.toString(LOCAL_PROXY_PORT));
|
||||
cmd.add("--server-host"); cmd.add(server);
|
||||
cmd.add("--server-port"); cmd.add(Integer.toString(port));
|
||||
cmd.add("--server-port-start"); cmd.add(Integer.toString(portStart));
|
||||
cmd.add("--server-port-end"); cmd.add(Integer.toString(portEnd));
|
||||
if (!token.isEmpty()) { cmd.add("--token"); cmd.add(token); }
|
||||
cmd.add("--transport"); cmd.add("chunk");
|
||||
cmd.add("--chunk-start"); cmd.add(Integer.toString(chunkMax));
|
||||
@@ -241,6 +268,12 @@ public class DragonService extends VpnService {
|
||||
cmd.add("--chunk-timeout"); cmd.add(timeout + "s");
|
||||
cmd.add("--chunk-grow-after"); cmd.add("16");
|
||||
cmd.add("--chunk-adapt-log=true");
|
||||
// Android always uses ascending startup calibration. MAX-FIRST remains
|
||||
// available in the Go CLI for compatibility but is intentionally never
|
||||
// enabled by the app. Runtime recovery is immediate and fine-grained.
|
||||
cmd.add("--chunk-max-first=false");
|
||||
cmd.add("--chunk-shrink-after"); cmd.add(Integer.toString(RUNTIME_SHRINK_AFTER));
|
||||
cmd.add("--chunk-shrink-step"); cmd.add(Integer.toString(RUNTIME_SHRINK_STEP));
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(cmd);
|
||||
pb.redirectErrorStream(true);
|
||||
@@ -271,11 +304,22 @@ public class DragonService extends VpnService {
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
// Keep the UI useful: adaptation changes and real errors only.
|
||||
handleCoreTelemetry(line);
|
||||
String lower = line.toLowerCase();
|
||||
if (line.startsWith("adaptive ") || line.startsWith("path probe:") || line.startsWith("wire") || lower.contains("error") || lower.contains("failed")) {
|
||||
if (line.startsWith("[D-TCP]") || line.startsWith("adaptive ")
|
||||
|| line.startsWith("path probe:") || line.startsWith("wire")
|
||||
|| lower.contains("error") || lower.contains("failed")) {
|
||||
AppLog.append(line);
|
||||
}
|
||||
if (line.startsWith("[D-TCP] phase=PORT_SCAN state=starting")) {
|
||||
publishState("TESTING PORTS", true);
|
||||
} else if (line.startsWith("[D-TCP] phase=AUTH state=starting")) {
|
||||
publishState("AUTHENTICATING", true);
|
||||
} else if (line.startsWith("[D-TCP] phase=CALIBRATION state=starting")) {
|
||||
publishState("TESTING CARRIER", true);
|
||||
} else if (line.startsWith("[D-TCP] phase=ACTIVE")) {
|
||||
publishState("TRANSPORT READY", true);
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
@@ -302,13 +346,92 @@ public class DragonService extends VpnService {
|
||||
watcher.start();
|
||||
}
|
||||
|
||||
private void waitForLocalProxy(Process process) throws Exception {
|
||||
long deadline = System.currentTimeMillis() + 10_000;
|
||||
private void handleCoreTelemetry(String line) {
|
||||
if (line == null || line.isEmpty()) return;
|
||||
String lower = line.toLowerCase();
|
||||
|
||||
if (line.startsWith("[D-TCP] phase=PORT_SCAN") && line.contains("state=success")) {
|
||||
int port = parseNamedInt(line, "port=");
|
||||
if (port > 0) updateWorkingPort(port);
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.startsWith("[D-TCP] phase=ACTIVE")) {
|
||||
int up = parseNamedInt(line, "upload_chunk=");
|
||||
int down = parseNamedInt(line, "download_chunk=");
|
||||
updateLiveChunks(up, down);
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.startsWith("[D-TCP] phase=CALIBRATION fake_iperf=")) {
|
||||
int chunk = parseNamedInt(line, "chunk=");
|
||||
if (chunk > 0) {
|
||||
if (lower.contains("fake_iperf=upload")) updateLiveChunks(chunk, 0);
|
||||
else if (lower.contains("fake_iperf=download")) updateLiveChunks(0, chunk);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.startsWith("adaptive ") && line.contains(" chunk:")) {
|
||||
int arrow = line.indexOf("->");
|
||||
if (arrow >= 0) {
|
||||
int value = parseLeadingInt(line, arrow + 2);
|
||||
if (value > 0) {
|
||||
if (lower.contains("upload chunk:")) updateLiveChunks(value, 0);
|
||||
else if (lower.contains("download chunk:")) updateLiveChunks(0, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int parseNamedInt(String line, String key) {
|
||||
int at = line.indexOf(key);
|
||||
if (at < 0) return 0;
|
||||
return parseLeadingInt(line, at + key.length());
|
||||
}
|
||||
|
||||
private static int parseLeadingInt(String line, int start) {
|
||||
int i = start;
|
||||
while (i < line.length() && Character.isWhitespace(line.charAt(i))) i++;
|
||||
int value = 0;
|
||||
boolean found = false;
|
||||
while (i < line.length()) {
|
||||
char c = line.charAt(i);
|
||||
if (c < '0' || c > '9') break;
|
||||
found = true;
|
||||
value = value * 10 + (c - '0');
|
||||
i++;
|
||||
}
|
||||
return found ? value : 0;
|
||||
}
|
||||
|
||||
private void updateLiveChunks(int up, int down) {
|
||||
boolean changed = false;
|
||||
if (up > 0 && up != appUpChunk) {
|
||||
appUpChunk = up;
|
||||
changed = true;
|
||||
}
|
||||
if (down > 0 && down != appDownChunk) {
|
||||
appDownChunk = down;
|
||||
changed = true;
|
||||
}
|
||||
if (changed) publishState(appStatus, appActive);
|
||||
}
|
||||
|
||||
private void updateWorkingPort(int port) {
|
||||
if (port > 0 && port != appWorkingPort) {
|
||||
appWorkingPort = port;
|
||||
publishState(appStatus, appActive);
|
||||
}
|
||||
}
|
||||
|
||||
private void waitForLocalProxy(Process process, int localPort) throws Exception {
|
||||
long deadline = System.currentTimeMillis() + 300_000;
|
||||
Throwable last = null;
|
||||
while (System.currentTimeMillis() < deadline) {
|
||||
if (!process.isAlive()) throw new IllegalStateException("DragonTCP core exited before proxy startup");
|
||||
try (Socket socket = new Socket()) {
|
||||
socket.connect(new InetSocketAddress("127.0.0.1", LOCAL_PROXY_PORT), 150);
|
||||
socket.connect(new InetSocketAddress("127.0.0.1", localPort), 150);
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
last = t;
|
||||
@@ -408,6 +531,9 @@ public class DragonService extends VpnService {
|
||||
stopping = true;
|
||||
cleanupComponentsLocked();
|
||||
if (logMessage != null) AppLog.append(logMessage);
|
||||
appUpChunk = 0;
|
||||
appDownChunk = 0;
|
||||
appWorkingPort = 0;
|
||||
publishState("DISCONNECTED", false);
|
||||
try { stopForeground(true); } catch (Throwable ignored) {}
|
||||
stopSelf();
|
||||
@@ -438,6 +564,9 @@ public class DragonService extends VpnService {
|
||||
state.setPackage(getPackageName());
|
||||
state.putExtra(EXTRA_ACTIVE, active);
|
||||
state.putExtra(EXTRA_STATUS, appStatus);
|
||||
state.putExtra(EXTRA_UP_CHUNK, appUpChunk);
|
||||
state.putExtra(EXTRA_DOWN_CHUNK, appDownChunk);
|
||||
state.putExtra(EXTRA_WORKING_PORT, appWorkingPort);
|
||||
try { sendBroadcast(state); } catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,10 +48,9 @@ public class MainActivity extends Activity {
|
||||
private static final int BAD = Color.rgb(232, 116, 116);
|
||||
|
||||
private EditText server;
|
||||
private EditText port;
|
||||
private EditText portStart;
|
||||
private EditText portEnd;
|
||||
private EditText token;
|
||||
private EditText chunkMax;
|
||||
private EditText chunkMin;
|
||||
private EditText batchMax;
|
||||
private EditText batchMin;
|
||||
private EditText reconnect;
|
||||
@@ -70,6 +69,9 @@ public class MainActivity extends Activity {
|
||||
private Button stopButton;
|
||||
private Button logsButton;
|
||||
private TextView statusChip;
|
||||
private TextView upChunkValue;
|
||||
private TextView downChunkValue;
|
||||
private TextView workingPortValue;
|
||||
|
||||
private boolean receiverRegistered;
|
||||
private boolean connectPending;
|
||||
@@ -80,8 +82,13 @@ public class MainActivity extends Activity {
|
||||
if (!DragonService.ACTION_STATE.equals(intent.getAction())) return;
|
||||
boolean active = intent.getBooleanExtra(DragonService.EXTRA_ACTIVE, false);
|
||||
String status = intent.getStringExtra(DragonService.EXTRA_STATUS);
|
||||
int upChunk = intent.getIntExtra(DragonService.EXTRA_UP_CHUNK, DragonService.currentUpChunk());
|
||||
int downChunk = intent.getIntExtra(DragonService.EXTRA_DOWN_CHUNK, DragonService.currentDownChunk());
|
||||
int workingPort = intent.getIntExtra(DragonService.EXTRA_WORKING_PORT, DragonService.currentWorkingPort());
|
||||
connectPending = false;
|
||||
updateConnectionUi(active, status == null ? (active ? "CONNECTED" : "DISCONNECTED") : status);
|
||||
updateLiveChunkUi(upChunk, downChunk);
|
||||
updateWorkingPortUi(workingPort);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -111,6 +118,8 @@ public class MainActivity extends Activity {
|
||||
receiverRegistered = true;
|
||||
}
|
||||
updateConnectionUi(DragonService.isActive(), DragonService.currentStatus());
|
||||
updateLiveChunkUi(DragonService.currentUpChunk(), DragonService.currentDownChunk());
|
||||
updateWorkingPortUi(DragonService.currentWorkingPort());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -216,10 +225,12 @@ public class MainActivity extends Activity {
|
||||
// ---------------------------------------------------------- connection
|
||||
LinearLayout connectionCard = card("CONNECTION");
|
||||
server = addField(connectionCard, "Server", "Server IP or hostname", "", false, false);
|
||||
LinearLayout connectionRow = row();
|
||||
port = addFieldToRow(connectionRow, "Port", "53", "53", true, false, 0.34f);
|
||||
token = addFieldToRow(connectionRow, "Token", "Optional", "", false, true, 0.66f);
|
||||
connectionCard.addView(connectionRow);
|
||||
LinearLayout portRow = row();
|
||||
portStart = addFieldToRow(portRow, "Port start", "53", "53", true, false, 0.5f);
|
||||
portEnd = addFieldToRow(portRow, "Port end", "53", "53", true, false, 0.5f);
|
||||
connectionCard.addView(portRow);
|
||||
token = addField(connectionCard, "Token", "Optional", "", false, true);
|
||||
connectionCard.addView(hint("Ports are tested from start to end. A port is accepted only after a real DragonTCP wire/header probe succeeds."));
|
||||
settings.addView(connectionCard, cardParams());
|
||||
|
||||
// ---------------------------------------------------------------- wire
|
||||
@@ -246,14 +257,65 @@ public class MainActivity extends Activity {
|
||||
wireBP.setOnClickListener(v -> setWireMode("bp"));
|
||||
wireX.setOnClickListener(v -> setWireMode("x"));
|
||||
|
||||
// --------------------------------------------------------- record size
|
||||
LinearLayout sizeCard = card("RECORD SIZE (BYTES)");
|
||||
LinearLayout chunks = row();
|
||||
chunkMax = addFieldToRow(chunks, "Max chunk", "1048576", "1048576", true, false, 0.58f);
|
||||
chunkMin = addFieldToRow(chunks, "Min chunk", "32", "32", true, false, 0.42f);
|
||||
sizeCard.addView(chunks);
|
||||
sizeCard.addView(hint("Probed automatically on connect, then adapted if the path changes."));
|
||||
settings.addView(sizeCard, cardParams());
|
||||
// ------------------------------------------------------ live chunk size
|
||||
LinearLayout liveCard = card("LIVE TRANSPORT");
|
||||
liveCard.addView(hint(
|
||||
"Working port and UP/DW chunk sizes update directly from the DragonTCP core, even when the log screen is closed."
|
||||
));
|
||||
LinearLayout liveRow = row();
|
||||
|
||||
LinearLayout upBox = new LinearLayout(this);
|
||||
upBox.setOrientation(LinearLayout.VERTICAL);
|
||||
upBox.setGravity(Gravity.CENTER);
|
||||
upBox.setPadding(dp(8), dp(10), dp(8), dp(10));
|
||||
upBox.setBackground(roundRect(FIELD, 12, BORDER, 1));
|
||||
TextView upLabel = text("UP CHUNK", 11, MUTED, true);
|
||||
upLabel.setGravity(Gravity.CENTER);
|
||||
upChunkValue = text("--", 18, OK, true);
|
||||
upChunkValue.setGravity(Gravity.CENTER);
|
||||
upChunkValue.setPadding(0, dp(4), 0, 0);
|
||||
upBox.addView(upLabel);
|
||||
upBox.addView(upChunkValue);
|
||||
|
||||
LinearLayout downBox = new LinearLayout(this);
|
||||
downBox.setOrientation(LinearLayout.VERTICAL);
|
||||
downBox.setGravity(Gravity.CENTER);
|
||||
downBox.setPadding(dp(8), dp(10), dp(8), dp(10));
|
||||
downBox.setBackground(roundRect(FIELD, 12, BORDER, 1));
|
||||
TextView downLabel = text("DW CHUNK", 11, MUTED, true);
|
||||
downLabel.setGravity(Gravity.CENTER);
|
||||
downChunkValue = text("--", 18, ACCENT, true);
|
||||
downChunkValue.setGravity(Gravity.CENTER);
|
||||
downChunkValue.setPadding(0, dp(4), 0, 0);
|
||||
downBox.addView(downLabel);
|
||||
downBox.addView(downChunkValue);
|
||||
|
||||
LinearLayout portBox = new LinearLayout(this);
|
||||
portBox.setOrientation(LinearLayout.VERTICAL);
|
||||
portBox.setGravity(Gravity.CENTER);
|
||||
portBox.setPadding(dp(8), dp(10), dp(8), dp(10));
|
||||
portBox.setBackground(roundRect(FIELD, 12, BORDER, 1));
|
||||
TextView portLabel = text("WORKING PORT", 11, MUTED, true);
|
||||
portLabel.setGravity(Gravity.CENTER);
|
||||
workingPortValue = text("--", 18, WARN, true);
|
||||
workingPortValue.setGravity(Gravity.CENTER);
|
||||
workingPortValue.setPadding(0, dp(4), 0, 0);
|
||||
portBox.addView(portLabel);
|
||||
portBox.addView(workingPortValue);
|
||||
|
||||
LinearLayout.LayoutParams upParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f);
|
||||
upParams.setMarginEnd(dp(4));
|
||||
LinearLayout.LayoutParams downParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f);
|
||||
downParams.setMarginStart(dp(4));
|
||||
downParams.setMarginEnd(dp(4));
|
||||
LinearLayout.LayoutParams portParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f);
|
||||
portParams.setMarginStart(dp(4));
|
||||
liveRow.addView(upBox, upParams);
|
||||
liveRow.addView(downBox, downParams);
|
||||
liveRow.addView(portBox, portParams);
|
||||
liveRow.setPadding(0, dp(8), 0, 0);
|
||||
liveCard.addView(liveRow);
|
||||
settings.addView(liveCard, cardParams());
|
||||
|
||||
// ------------------------------------------------------- download batch
|
||||
LinearLayout batchCard = card("DOWNLOAD BATCH");
|
||||
@@ -291,13 +353,13 @@ public class MainActivity extends Activity {
|
||||
timeout = addFieldToRow(timing, "Timeout (s)", "5", "5", true, false, 0.42f);
|
||||
advancedCard.addView(timing);
|
||||
LinearLayout probing = row();
|
||||
probeDelay = addFieldToRow(probing, "Probe delay (ms)", "1000", "1000", true, false, 0.65f);
|
||||
probeDelay = addFieldToRow(probing, "Probe delay (ms)", "100", "100", true, false, 0.65f);
|
||||
probeThreads = addFieldToRow(probing, "Probe threads", "1", "1", true, false, 0.35f);
|
||||
advancedCard.addView(probing);
|
||||
advancedCard.addView(hint(
|
||||
"1 = auto (recommended: starts persistent, then learns one request per "
|
||||
+ "connection only if reuse really fails) • 0 = persistent • N = rotate every N requests\n"
|
||||
+ "Probe delay spaces startup profile connections; 1000 ms is recommended. "
|
||||
+ "Probe delay spaces the single-poller valid-mask scan; 100 ms is recommended. "
|
||||
+ "Probe threads defaults to 1; more may trigger carrier limits."));
|
||||
settings.addView(advancedCard, cardParams());
|
||||
|
||||
@@ -323,7 +385,8 @@ public class MainActivity extends Activity {
|
||||
settings.addView(logsButton, logsParams);
|
||||
|
||||
TextView note = text(
|
||||
"IPv4 uses the Android VPN adapter. IPv6 is captured and blocked to prevent bypass. DNS is tunneled to 1.1.1.1 over DragonTCP.",
|
||||
"IPv4 uses the Android VPN adapter. IPv6 is captured and blocked to prevent bypass. "
|
||||
+ "Android uses direct DragonTCP mode; SSH remains in the project/server but is disabled in this app build.",
|
||||
11, MUTED, false
|
||||
);
|
||||
note.setLineSpacing(0, 1.08f);
|
||||
@@ -345,6 +408,23 @@ public class MainActivity extends Activity {
|
||||
updateConnectionUi(false, "DISCONNECTED");
|
||||
}
|
||||
|
||||
private void updateLiveChunkUi(int up, int down) {
|
||||
if (upChunkValue != null) upChunkValue.setText(formatChunk(up));
|
||||
if (downChunkValue != null) downChunkValue.setText(formatChunk(down));
|
||||
}
|
||||
|
||||
private void updateWorkingPortUi(int value) {
|
||||
if (workingPortValue != null) workingPortValue.setText(value > 0 ? Integer.toString(value) : "--");
|
||||
}
|
||||
|
||||
private String formatChunk(int bytes) {
|
||||
if (bytes <= 0) return "--";
|
||||
if (bytes == 1024 * 1024) return "1 MiB\n1048576 B";
|
||||
if (bytes >= 1024) return (bytes / 1024) + " KiB\n" + bytes + " B";
|
||||
return bytes + " B";
|
||||
}
|
||||
|
||||
|
||||
/** Describes the batch configuration in words, live, as the user types. */
|
||||
private void updateBatchHint() {
|
||||
if (batchHint == null) return;
|
||||
@@ -486,16 +566,16 @@ public class MainActivity extends Activity {
|
||||
if (wireHint == null) return;
|
||||
if ("auto".equals(mode)) {
|
||||
wireHint.setTextColor(ACCENT);
|
||||
wireHint.setText("Auto: discovers a working B/BP/X profile and keeps it until restart.");
|
||||
wireHint.setText("Auto: scans only direct-valid masked headers (B/BP: 32, X: 96) and keeps the first working profile.");
|
||||
} else if ("b".equals(mode)) {
|
||||
wireHint.setTextColor(OK);
|
||||
wireHint.setText("B only; header profile is discovered automatically.");
|
||||
wireHint.setText("B only; scans the 32 valid masks: 00,08,10,...,F8.");
|
||||
} else if ("bp".equals(mode)) {
|
||||
wireHint.setTextColor(OK);
|
||||
wireHint.setText("BP only.");
|
||||
wireHint.setText("BP only; scans the 32 valid masks: 00,08,10,...,F8.");
|
||||
} else {
|
||||
wireHint.setTextColor(OK);
|
||||
wireHint.setText("X only; header profile is discovered automatically.");
|
||||
wireHint.setText("X only; scans only masks valid for the direct UP classifier (96 values).");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -586,16 +666,16 @@ public class MainActivity extends Activity {
|
||||
SharedPreferences p = getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||
Intent i = new Intent(this, DragonService.class).setAction(DragonService.ACTION_CONNECT);
|
||||
i.putExtra(DragonService.EXTRA_SERVER, p.getString("server", ""));
|
||||
i.putExtra(DragonService.EXTRA_PORT, p.getInt("port", 53));
|
||||
int legacyPort = p.getInt("port", 53);
|
||||
i.putExtra(DragonService.EXTRA_PORT_START, p.getInt("portStart", legacyPort));
|
||||
i.putExtra(DragonService.EXTRA_PORT_END, p.getInt("portEnd", legacyPort));
|
||||
i.putExtra(DragonService.EXTRA_TOKEN, p.getString("token", ""));
|
||||
i.putExtra(DragonService.EXTRA_WIRE, p.getString("wire", "auto"));
|
||||
i.putExtra(DragonService.EXTRA_CHUNK_MAX, p.getInt("max", 1048576));
|
||||
i.putExtra(DragonService.EXTRA_CHUNK_MIN, p.getInt("min", 32));
|
||||
i.putExtra(DragonService.EXTRA_BATCH_MAX, p.getInt("batchMax", 1));
|
||||
i.putExtra(DragonService.EXTRA_BATCH_MIN, p.getInt("batchMin", 1));
|
||||
i.putExtra(DragonService.EXTRA_RECONNECT, p.getInt("reconnect", 1));
|
||||
i.putExtra(DragonService.EXTRA_TIMEOUT, p.getInt("timeout", 5));
|
||||
i.putExtra(DragonService.EXTRA_PROBE_DELAY, p.getInt("probeDelay", 1000));
|
||||
i.putExtra(DragonService.EXTRA_PROBE_DELAY, p.getInt("probeDelay", 100));
|
||||
i.putExtra(DragonService.EXTRA_PROBE_THREADS, p.getInt("probeThreads", 1));
|
||||
if (Build.VERSION.SDK_INT >= 26) startForegroundService(i); else startService(i);
|
||||
}
|
||||
@@ -603,30 +683,35 @@ public class MainActivity extends Activity {
|
||||
private void validateAndSave() {
|
||||
String h = server.getText().toString().trim();
|
||||
if (h.isEmpty()) throw new IllegalArgumentException("Server is required");
|
||||
int p = parse(port, 1, 65535, "Port");
|
||||
int max = parse(chunkMax, 32, 1048576, "Max chunk");
|
||||
int min = parse(chunkMin, 32, max, "Min chunk");
|
||||
int pStart = parse(portStart, 1, 65535, "Port start");
|
||||
int pEnd = parse(portEnd, 1, 65535, "Port end");
|
||||
if (pStart > pEnd) throw new IllegalArgumentException("Port start must not exceed port end");
|
||||
int bMax = parse(batchMax, 1, BATCH_LIMIT, "Batch max");
|
||||
int bMin = parse(batchMin, 1, BATCH_LIMIT, "Batch min");
|
||||
if (bMin > bMax) throw new IllegalArgumentException("Batch min must not exceed batch max");
|
||||
int rec = parse(reconnect, 0, 1000000, "Reconnect every");
|
||||
int tout = parse(timeout, 1, 120, "Timeout");
|
||||
int delay = parse(probeDelay, 200, 30000, "Probe delay");
|
||||
int delay = parse(probeDelay, 50, 30000, "Probe delay");
|
||||
int threads = parse(probeThreads, 1, 16, "Probe threads");
|
||||
|
||||
getSharedPreferences(PREFS, MODE_PRIVATE).edit()
|
||||
.putString("server", h)
|
||||
.putInt("port", p)
|
||||
.putInt("portStart", pStart)
|
||||
.putInt("portEnd", pEnd)
|
||||
.putInt("port", pStart)
|
||||
.putString("token", token.getText().toString())
|
||||
.putString("wire", wireMode)
|
||||
.putInt("max", max)
|
||||
.putInt("min", min)
|
||||
.putInt("batchMax", bMax)
|
||||
.putInt("batchMin", bMin)
|
||||
.putInt("reconnect", rec)
|
||||
.putInt("timeout", tout)
|
||||
.putInt("probeDelay", delay)
|
||||
.putInt("probeThreads", threads)
|
||||
.remove("sshUser")
|
||||
.remove("sshPassword")
|
||||
.remove("forceMaxFirst")
|
||||
.remove("forceClearPayload")
|
||||
.remove("max")
|
||||
.remove("min")
|
||||
.apply();
|
||||
}
|
||||
|
||||
@@ -641,17 +726,29 @@ public class MainActivity extends Activity {
|
||||
private void loadSettings() {
|
||||
SharedPreferences p = getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||
server.setText(p.getString("server", ""));
|
||||
port.setText(Integer.toString(p.getInt("port", 53)));
|
||||
int legacyPort = p.getInt("port", 53);
|
||||
portStart.setText(Integer.toString(p.getInt("portStart", legacyPort)));
|
||||
portEnd.setText(Integer.toString(p.getInt("portEnd", legacyPort)));
|
||||
token.setText(p.getString("token", ""));
|
||||
setWireMode(p.getString("wire", "auto"));
|
||||
chunkMax.setText(Integer.toString(p.getInt("max", 1048576)));
|
||||
chunkMin.setText(Integer.toString(p.getInt("min", 32)));
|
||||
batchMax.setText(Integer.toString(p.getInt("batchMax", 1)));
|
||||
batchMin.setText(Integer.toString(p.getInt("batchMin", 1)));
|
||||
reconnect.setText(Integer.toString(p.getInt("reconnect", 1)));
|
||||
timeout.setText(Integer.toString(p.getInt("timeout", 5)));
|
||||
probeDelay.setText(Integer.toString(p.getInt("probeDelay", 1000)));
|
||||
probeDelay.setText(Integer.toString(p.getInt("probeDelay", 100)));
|
||||
probeThreads.setText(Integer.toString(p.getInt("probeThreads", 1)));
|
||||
// Remove legacy app-only SSH/MAX-FIRST preferences. SSH support remains
|
||||
// in the Go project, but this Android UI intentionally never enables it.
|
||||
p.edit()
|
||||
.remove("sshUser")
|
||||
.remove("sshPassword")
|
||||
.remove("forceMaxFirst")
|
||||
.remove("forceClearPayload")
|
||||
.remove("max")
|
||||
.remove("min")
|
||||
.apply();
|
||||
updateLiveChunkUi(DragonService.currentUpChunk(), DragonService.currentDownChunk());
|
||||
updateWorkingPortUi(DragonService.currentWorkingPort());
|
||||
}
|
||||
|
||||
private void updateConnectionUi(boolean active, String rawStatus) {
|
||||
@@ -660,7 +757,7 @@ public class MainActivity extends Activity {
|
||||
boolean connected = status.contains("CONNECTED") && !status.contains("DISCONNECTED");
|
||||
boolean starting = active && !connected && !stopping;
|
||||
|
||||
statusChip.setText(connected ? "CONNECTED" : starting ? "CONNECTING" : stopping ? "STOPPING" : "DISCONNECTED");
|
||||
statusChip.setText(connected ? "CONNECTED" : stopping ? "STOPPING" : starting ? status : "DISCONNECTED");
|
||||
statusChip.setTextColor(connected ? OK : starting || stopping ? WARN : MUTED);
|
||||
statusChip.setBackground(roundRect(
|
||||
connected ? Color.rgb(20, 54, 42) : starting || stopping ? Color.rgb(58, 43, 22) : CARD,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package tech.xvanturing.freeproxy.vpn.proxy
|
||||
|
||||
import tech.xvanturing.freeproxy.data.model.ProxyProfile
|
||||
import tech.xvanturing.freeproxy.data.model.ProxyType
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.net.DatagramSocket
|
||||
@@ -9,12 +10,14 @@ import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Socket
|
||||
|
||||
private fun Byte.u8(): Int = toInt() and 0xFF
|
||||
|
||||
/**
|
||||
* Minimal HTTP CONNECT upstream client used by DragonTCP Lite.
|
||||
* Upstream proxy client used by the userspace VPN engine.
|
||||
*
|
||||
* The upstream proxy is always the local DragonTCP Go process on
|
||||
* 127.0.0.1:8080. DragonTCP then carries the stream over adaptive XOR-framed
|
||||
* TCP/53 to the remote server.
|
||||
* Direct DragonTCP mode talks HTTP CONNECT to the local Go core. SSH mode uses
|
||||
* the Go core's local SOCKS5 listener; TCP CONNECT becomes SSH direct-tcpip and
|
||||
* UDP ASSOCIATE is bridged to the server's BadVPN-compatible UDPGW.
|
||||
*/
|
||||
class ProxyClient(
|
||||
private val profile: ProxyProfile,
|
||||
@@ -23,16 +26,30 @@ class ProxyClient(
|
||||
) {
|
||||
@Throws(IOException::class)
|
||||
fun connectTcp(destination: InetAddress, destinationPort: Int): Socket {
|
||||
val socket = Socket()
|
||||
try {
|
||||
socket.bind(InetSocketAddress(0))
|
||||
if (!protector.protect(socket)) {
|
||||
throw IOException("Unable to protect local proxy socket from VPN")
|
||||
}
|
||||
socket.connect(proxyAddress, CONNECT_TIMEOUT_MS)
|
||||
socket.soTimeout = HANDSHAKE_TIMEOUT_MS
|
||||
socket.tcpNoDelay = true
|
||||
return when (profile.type) {
|
||||
ProxyType.HTTP -> connectHttp(destination, destinationPort)
|
||||
ProxyType.SOCKS5 -> connectSocks(destination, destinationPort)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun newProtectedSocket(): Socket {
|
||||
val socket = Socket()
|
||||
socket.bind(InetSocketAddress(0))
|
||||
if (!protector.protect(socket)) {
|
||||
socket.close()
|
||||
throw IOException("Unable to protect local proxy socket from VPN")
|
||||
}
|
||||
socket.connect(proxyAddress, CONNECT_TIMEOUT_MS)
|
||||
socket.soTimeout = HANDSHAKE_TIMEOUT_MS
|
||||
socket.tcpNoDelay = true
|
||||
return socket
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun connectHttp(destination: InetAddress, destinationPort: Int): Socket {
|
||||
val socket = newProtectedSocket()
|
||||
try {
|
||||
val literal = if (destination is Inet6Address) {
|
||||
"[${destination.hostAddress}]:$destinationPort"
|
||||
} else {
|
||||
@@ -60,7 +77,6 @@ class ProxyClient(
|
||||
if (code !in 200..299) {
|
||||
throw IOException("Local DragonTCP CONNECT failed: $status")
|
||||
}
|
||||
|
||||
socket.soTimeout = 0
|
||||
return socket
|
||||
} catch (t: Throwable) {
|
||||
@@ -69,16 +85,113 @@ class ProxyClient(
|
||||
}
|
||||
}
|
||||
|
||||
/** HTTP CONNECT does not support SOCKS5 UDP ASSOCIATE. */
|
||||
@Throws(IOException::class)
|
||||
private fun connectSocks(destination: InetAddress, destinationPort: Int): Socket {
|
||||
val socket = newProtectedSocket()
|
||||
try {
|
||||
socksGreeting(socket)
|
||||
val out = socket.getOutputStream()
|
||||
out.write(byteArrayOf(SOCKS_VERSION.toByte(), CMD_CONNECT.toByte(), 0))
|
||||
writeSocksAddress(out = out, address = destination, port = destinationPort)
|
||||
out.flush()
|
||||
readSocksReply(socket.getInputStream())
|
||||
socket.soTimeout = 0
|
||||
return socket
|
||||
} catch (t: Throwable) {
|
||||
runCatching { socket.close() }
|
||||
throw t
|
||||
}
|
||||
}
|
||||
|
||||
/** Opens a SOCKS5 UDP ASSOCIATE control connection and returns its relay endpoint. */
|
||||
@Throws(IOException::class)
|
||||
fun openUdpAssociate(): UdpAssociation {
|
||||
throw IOException("UDP ASSOCIATE unavailable with local HTTP CONNECT proxy")
|
||||
if (profile.type != ProxyType.SOCKS5) {
|
||||
throw IOException("UDP ASSOCIATE requires the local SOCKS5 proxy")
|
||||
}
|
||||
val socket = newProtectedSocket()
|
||||
try {
|
||||
socksGreeting(socket)
|
||||
socket.getOutputStream().apply {
|
||||
write(byteArrayOf(SOCKS_VERSION.toByte(), CMD_UDP_ASSOCIATE.toByte(), 0, ATYP_IPV4.toByte()))
|
||||
write(byteArrayOf(0, 0, 0, 0, 0, 0))
|
||||
flush()
|
||||
}
|
||||
val relay = readSocksReply(socket.getInputStream())
|
||||
socket.soTimeout = 0
|
||||
return UdpAssociation(socket, relay)
|
||||
} catch (t: Throwable) {
|
||||
runCatching { socket.close() }
|
||||
throw t
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun socksGreeting(socket: Socket) {
|
||||
socket.getOutputStream().apply {
|
||||
write(byteArrayOf(SOCKS_VERSION.toByte(), 1, METHOD_NO_AUTH.toByte()))
|
||||
flush()
|
||||
}
|
||||
val response = socket.getInputStream().readExactly(2)
|
||||
if (response[0].u8() != SOCKS_VERSION || response[1].u8() != METHOD_NO_AUTH) {
|
||||
throw IOException("Local SOCKS5 proxy rejected no-auth negotiation")
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun readSocksReply(input: InputStream): InetSocketAddress {
|
||||
val header = input.readExactly(4)
|
||||
if (header[0].u8() != SOCKS_VERSION) throw IOException("Invalid SOCKS5 reply version")
|
||||
if (header[1].u8() != 0) throw IOException("SOCKS5 request failed: reply=${header[1].u8()}")
|
||||
if (header[2].u8() != 0) throw IOException("Invalid SOCKS5 reserved byte")
|
||||
|
||||
val host = when (header[3].u8()) {
|
||||
ATYP_IPV4 -> InetAddress.getByAddress(input.readExactly(4)).hostAddress
|
||||
ATYP_IPV6 -> InetAddress.getByAddress(input.readExactly(16)).hostAddress
|
||||
ATYP_DOMAIN -> {
|
||||
val length = input.readExactly(1)[0].u8()
|
||||
if (length == 0) throw IOException("Empty SOCKS5 relay domain")
|
||||
String(input.readExactly(length), Charsets.ISO_8859_1)
|
||||
}
|
||||
else -> throw IOException("Unsupported SOCKS5 relay address type")
|
||||
}
|
||||
val portBytes = input.readExactly(2)
|
||||
val port = (portBytes[0].u8() shl 8) or portBytes[1].u8()
|
||||
if (port == 0) throw IOException("SOCKS5 relay returned port 0")
|
||||
|
||||
// RFC 1928 permits 0.0.0.0 as BND.ADDR. For a local SOCKS listener,
|
||||
// the usable endpoint is then the same host we connected to.
|
||||
val relayHost = if (host == "0.0.0.0" || host == "::" || host == "0:0:0:0:0:0:0:0") {
|
||||
proxyAddress.address?.hostAddress ?: proxyAddress.hostString
|
||||
} else host
|
||||
return InetSocketAddress(relayHost, port)
|
||||
}
|
||||
|
||||
private fun writeSocksAddress(out: java.io.OutputStream, address: InetAddress, port: Int) {
|
||||
val raw = address.address
|
||||
when (raw.size) {
|
||||
4 -> {
|
||||
out.write(ATYP_IPV4)
|
||||
out.write(raw)
|
||||
}
|
||||
16 -> {
|
||||
out.write(ATYP_IPV6)
|
||||
out.write(raw)
|
||||
}
|
||||
else -> throw IOException("Unsupported IP address length ${raw.size}")
|
||||
}
|
||||
out.write((port ushr 8) and 0xFF)
|
||||
out.write(port and 0xFF)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ATYP_IPV4 = 0x01
|
||||
const val ATYP_DOMAIN = 0x03
|
||||
const val ATYP_IPV6 = 0x04
|
||||
private const val SOCKS_VERSION = 0x05
|
||||
private const val METHOD_NO_AUTH = 0x00
|
||||
private const val CMD_CONNECT = 0x01
|
||||
private const val CMD_UDP_ASSOCIATE = 0x03
|
||||
private const val CONNECT_TIMEOUT_MS = 10_000
|
||||
private const val HANDSHAKE_TIMEOUT_MS = 15_000
|
||||
private const val MAX_HEADER_LINE = 8192
|
||||
|
||||
Reference in New Issue
Block a user