ssh-auto-forward 0.0.2__tar.gz → 0.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. {ssh_auto_forward-0.0.2 → ssh_auto_forward-0.0.3}/.github/workflows/test.yml +6 -2
  2. {ssh_auto_forward-0.0.2 → ssh_auto_forward-0.0.3}/Makefile +14 -1
  3. ssh_auto_forward-0.0.3/PKG-INFO +216 -0
  4. ssh_auto_forward-0.0.3/README.md +197 -0
  5. ssh_auto_forward-0.0.3/benchmarks/README.md +139 -0
  6. ssh_auto_forward-0.0.3/benchmarks/WINDOWS_PERFORMANCE_LIMITATION.md +68 -0
  7. ssh_auto_forward-0.0.3/benchmarks/bench_buffer_sweep.py +78 -0
  8. ssh_auto_forward-0.0.3/benchmarks/bench_common.py +239 -0
  9. ssh_auto_forward-0.0.3/benchmarks/bench_html_stress.py +142 -0
  10. ssh_auto_forward-0.0.3/benchmarks/bench_large_files.py +164 -0
  11. ssh_auto_forward-0.0.3/benchmarks/bench_remote.py +414 -0
  12. ssh_auto_forward-0.0.3/benchmarks/download_helper.py +135 -0
  13. ssh_auto_forward-0.0.3/benchmarks/http_server.py +103 -0
  14. ssh_auto_forward-0.0.3/benchmarks/nginx-benchmark.conf +24 -0
  15. ssh_auto_forward-0.0.3/benchmarks/results_buffer_sweep.json +50 -0
  16. ssh_auto_forward-0.0.3/benchmarks/results_html_stress.json +338 -0
  17. ssh_auto_forward-0.0.3/benchmarks/results_large_files.json +384 -0
  18. ssh_auto_forward-0.0.3/benchmarks/results_remote.json +12 -0
  19. ssh_auto_forward-0.0.3/benchmarks/simple_http_server.py +59 -0
  20. ssh_auto_forward-0.0.3/benchmarks/stream_generator.py +64 -0
  21. ssh_auto_forward-0.0.3/benchmarks/tornado_server.py +61 -0
  22. ssh_auto_forward-0.0.3/benchmarks/tunnel_helper.py +216 -0
  23. ssh_auto_forward-0.0.3/docker/Dockerfile +15 -0
  24. ssh_auto_forward-0.0.3/docker/Dockerfile.bench +35 -0
  25. ssh_auto_forward-0.0.3/docker/docker-compose.yml +6 -0
  26. ssh_auto_forward-0.0.3/docker/sshd_config +9 -0
  27. ssh_auto_forward-0.0.3/docker/test_key +27 -0
  28. ssh_auto_forward-0.0.3/docker/test_key.pub +1 -0
  29. ssh_auto_forward-0.0.3/docs/dashboard.svg +185 -0
  30. ssh_auto_forward-0.0.3/ssh_auto_forward/__version__.py +1 -0
  31. {ssh_auto_forward-0.0.2 → ssh_auto_forward-0.0.3}/ssh_auto_forward/cli.py +38 -13
  32. ssh_auto_forward-0.0.3/ssh_auto_forward/dashboard.py +1116 -0
  33. {ssh_auto_forward-0.0.2 → ssh_auto_forward-0.0.3}/ssh_auto_forward/forwarder.py +447 -44
  34. ssh_auto_forward-0.0.3/ssh_auto_forward/pipe.py +115 -0
  35. {ssh_auto_forward-0.0.2 → ssh_auto_forward-0.0.3}/tests/test_cli.py +156 -1
  36. ssh_auto_forward-0.0.3/tests_integration/Dockerfile.test +32 -0
  37. ssh_auto_forward-0.0.3/tests_integration/ssh_test_keys/id_rsa_test +38 -0
  38. ssh_auto_forward-0.0.3/tests_integration/ssh_test_keys/id_rsa_test.pub +1 -0
  39. ssh_auto_forward-0.0.3/tests_integration/test_auto_forward.py +788 -0
  40. ssh_auto_forward-0.0.3/tests_integration/test_dashboard.py +714 -0
  41. ssh_auto_forward-0.0.2/PKG-INFO +0 -166
  42. ssh_auto_forward-0.0.2/README.md +0 -147
  43. ssh_auto_forward-0.0.2/ssh_auto_forward/__version__.py +0 -1
  44. ssh_auto_forward-0.0.2/ssh_auto_forward/dashboard.py +0 -373
  45. ssh_auto_forward-0.0.2/tests_integration/test_auto_forward.py +0 -766
  46. ssh_auto_forward-0.0.2/tests_integration/test_dashboard.py +0 -200
  47. {ssh_auto_forward-0.0.2 → ssh_auto_forward-0.0.3}/.gitignore +0 -0
  48. {ssh_auto_forward-0.0.2 → ssh_auto_forward-0.0.3}/.python-version +0 -0
  49. {ssh_auto_forward-0.0.2 → ssh_auto_forward-0.0.3}/pyproject.toml +0 -0
  50. {ssh_auto_forward-0.0.2 → ssh_auto_forward-0.0.3}/ssh_auto_forward/__init__.py +0 -0
  51. {ssh_auto_forward-0.0.2 → ssh_auto_forward-0.0.3}/tests/__init__.py +0 -0
  52. {ssh_auto_forward-0.0.2 → ssh_auto_forward-0.0.3}/tests.md +0 -0
  53. {ssh_auto_forward-0.0.2 → ssh_auto_forward-0.0.3}/tests_integration/__init__.py +0 -0
@@ -14,6 +14,10 @@ jobs:
14
14
  with:
15
15
  python-version: ${{ matrix.python-version }}
16
16
  - run: pip install uv
17
- - run: uv sync
18
- - run: uv run pytest tests/
17
+ - run: uv sync --dev
18
+ - run: uv run pytest tests/ -v
19
+ - name: Build Docker test image
20
+ run: docker compose -f docker/docker-compose.yml build
21
+ - name: Run integration tests with Docker
22
+ run: uv run pytest tests_integration/ -v
19
23
  - run: uv run ruff check ssh_auto_forward/
@@ -1,4 +1,4 @@
1
- .PHONY: test setup shell coverage publish-build publish-test publish publish-clean run
1
+ .PHONY: test setup shell coverage publish-build publish-test publish publish-clean run docker-build docker-up docker-down
2
2
 
3
3
  test:
4
4
  uv run pytest
@@ -25,4 +25,17 @@ publish-clean:
25
25
  rm -r dist/
26
26
 
27
27
  run:
28
+ uv run python -m ssh_auto_forward.cli
29
+
30
+ hetzner:
28
31
  uv run python -m ssh_auto_forward.cli hetzner
32
+
33
+
34
+ docker-build:
35
+ docker compose -f docker/docker-compose.yml build
36
+
37
+ docker-up:
38
+ docker compose -f docker/docker-compose.yml up -d --build
39
+
40
+ docker-down:
41
+ docker compose -f docker/docker-compose.yml down
@@ -0,0 +1,216 @@
1
+ Metadata-Version: 2.4
2
+ Name: ssh-auto-forward
3
+ Version: 0.0.3
4
+ Summary: Auto-forward SSH ports
5
+ Author: alexe
6
+ License: WTFPL
7
+ Keywords: port-forwarding,remote-development,ssh,tunnel
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: paramiko>=3.4.0
17
+ Requires-Dist: textual>=8.0.0
18
+ Description-Content-Type: text/markdown
19
+
20
+ # SSH Auto Port Forwarder
21
+
22
+ Automatically detect and forward ports from a remote SSH server to your local machine. Similar to VS Code's port forwarding feature, but fully automatic.
23
+
24
+ ![Dashboard](docs/dashboard.svg)
25
+
26
+ ## Features
27
+
28
+ - Interactive TUI dashboard - View and manage tunnels in real-time
29
+ - Automatically discovers listening ports on the remote server
30
+ - Shows process names for each forwarded port
31
+ - Forwards ports to your local machine via SSH tunneling
32
+ - Handles port conflicts by finding alternative local ports
33
+ - Auto-detects new ports and starts forwarding
34
+ - Auto-detects closed ports and stops forwarding
35
+ - Reads connection details from your SSH config
36
+ - Skips well-known ports (< 1000) by default
37
+ - Configurable max auto-forward port (default: 10000)
38
+
39
+ ## Installation
40
+
41
+ ### With uv (recommended):
42
+
43
+ ```bash
44
+ uvx ssh-auto-forward hetzner
45
+ ```
46
+
47
+ ### Install with pip:
48
+
49
+ ```bash
50
+ pip install ssh-auto-forward
51
+ ```
52
+
53
+ ### Install locally:
54
+
55
+ ```bash
56
+ cd portforwards
57
+ uv sync
58
+ ```
59
+
60
+ ## Running
61
+
62
+ ### Dashboard mode (default):
63
+
64
+ ```bash
65
+ ssh-auto-forward hetzner
66
+ ```
67
+
68
+ ### CLI mode (headless):
69
+
70
+ ```bash
71
+ ssh-auto-forward hetzner --cli
72
+ ```
73
+
74
+ ### With uvx (no installation):
75
+
76
+ ```bash
77
+ uvx ssh-auto-forward hetzner
78
+ ```
79
+
80
+ ## Dashboard Controls
81
+
82
+ | Key | Action |
83
+ |-----|--------|
84
+ | X / Enter | Toggle port (open if closed, close if opened) |
85
+ | O | Open URL in browser (for forwarded ports) |
86
+ | R | Refresh port list |
87
+ | L | Toggle log panel |
88
+ | Q | Quit |
89
+
90
+ ## Options
91
+
92
+ ```
93
+ -v, --verbose Enable verbose logging
94
+ -i, --interval SECS Scan interval in seconds (default: 5)
95
+ -p, --port-range MIN:MAX Local port range for remapping (default: 3000:10000)
96
+ -s, --skip PORTS Comma-separated ports to skip (default: all ports < 1000)
97
+ -c, --config PATH Path to SSH config file
98
+ -m, --max-auto-port PORT Maximum port to auto-forward (default: 10000)
99
+ --include-configs Include ports already forwarded via SSH config LocalForward
100
+ --cli Run in CLI mode instead of dashboard
101
+ --version Show version and exit
102
+ ```
103
+
104
+ ## Examples
105
+
106
+ ```bash
107
+ # Dashboard mode (default)
108
+ ssh-auto-forward hetzner
109
+
110
+ # CLI mode
111
+ ssh-auto-forward hetzner --cli
112
+
113
+ # Scan every 3 seconds
114
+ ssh-auto-forward hetzner -i 3
115
+
116
+ # Use specific port range
117
+ ssh-auto-forward hetzner -p 4000:9000
118
+
119
+ # Skip specific ports
120
+ ssh-auto-forward hetzner -s 22,80,443
121
+
122
+ # Verbose mode
123
+ ssh-auto-forward hetzner -v
124
+
125
+ # Only auto-forward ports up to 5000 (higher ports shown but not auto-forwarded)
126
+ ssh-auto-forward hetzner -m 5000
127
+
128
+ # Include ports already forwarded via SSH config LocalForward
129
+ ssh-auto-forward hetzner --include-configs
130
+ ```
131
+
132
+ ## Performance
133
+
134
+ Throughput is 150-190 MB/s for large file transfers (45-70% of native `ssh -L`). HTML page latency is 2-3 ms median at up to 10 req/s with zero failures. The overhead comes from paramiko (Python) vs OpenSSH (C) - negligible for interactive use like web browsing.
135
+
136
+ See [benchmarks/](benchmarks/) for full methodology and results.
137
+
138
+ ## How it works
139
+
140
+ 1. Connects to your remote server using your SSH config
141
+ 2. Runs `ss -tlnp` on the remote to find listening ports
142
+ 3. Creates SSH tunnels for each discovered port
143
+ 4. Continuously monitors for new/closed ports
144
+ 5. Handles port conflicts on your local machine
145
+
146
+ ### SSH Config Integration
147
+
148
+ The tool reads your SSH config file (`~/.ssh/config`) for connection details. It also respects `LocalForward` directives:
149
+
150
+ ```ssh
151
+ Host myserver
152
+ HostName example.com
153
+ User myuser
154
+ LocalForward 8080 localhost:8080 # This port is excluded by default
155
+ LocalForward 3000 localhost:3000
156
+ ```
157
+
158
+ By default, ports that are already forwarded via `LocalForward` are **excluded** from the dashboard list since they're handled by SSH itself. Use `--include-configs` to show them.
159
+
160
+ ## Status messages
161
+
162
+ ```
163
+ ✓ Connected!
164
+ ✓ Forwarding port 2999 (python3)
165
+ ✓ Forwarding port 7681 (ttyd)
166
+ ✓ Forwarding remote port 19840 -> local port 3000 (node)
167
+ ✗ Remote port 2999 is no longer listening, stopping tunnel
168
+ ```
169
+
170
+ ## Testing
171
+
172
+ Start a test server on your remote machine:
173
+
174
+ ```bash
175
+ ssh hetzner "python3 -m http.server 9999 --bind 127.0.0.1 &"
176
+ ```
177
+
178
+ Then run `ssh-auto-forward hetzner` and access it locally:
179
+
180
+ ```bash
181
+ curl http://localhost:9999/
182
+ ```
183
+
184
+ ## Stopping
185
+
186
+ - Dashboard: Press `Q`
187
+ - CLI mode: Press `Ctrl+C`
188
+
189
+ ## Requirements
190
+
191
+ - Python 3.10+
192
+ - paramiko
193
+ - textual (for TUI dashboard)
194
+ - Remote server must have `ss` or `netstat` command available
195
+
196
+ ## Tests
197
+
198
+ ### Unit tests (run locally, no SSH required):
199
+
200
+ ```bash
201
+ uv run pytest tests/ -v
202
+ ```
203
+
204
+ ### Integration tests (Docker, default):
205
+
206
+ ```bash
207
+ uv run pytest tests_integration/ -v
208
+ ```
209
+
210
+ This starts a Docker container with SSH server and tests against it.
211
+
212
+ ### Integration tests (real SSH server):
213
+
214
+ ```bash
215
+ SSH_AUTO_FORWARD_TEST_HOST=your-server uv run pytest tests_integration/ -v
216
+ ```
@@ -0,0 +1,197 @@
1
+ # SSH Auto Port Forwarder
2
+
3
+ Automatically detect and forward ports from a remote SSH server to your local machine. Similar to VS Code's port forwarding feature, but fully automatic.
4
+
5
+ ![Dashboard](docs/dashboard.svg)
6
+
7
+ ## Features
8
+
9
+ - Interactive TUI dashboard - View and manage tunnels in real-time
10
+ - Automatically discovers listening ports on the remote server
11
+ - Shows process names for each forwarded port
12
+ - Forwards ports to your local machine via SSH tunneling
13
+ - Handles port conflicts by finding alternative local ports
14
+ - Auto-detects new ports and starts forwarding
15
+ - Auto-detects closed ports and stops forwarding
16
+ - Reads connection details from your SSH config
17
+ - Skips well-known ports (< 1000) by default
18
+ - Configurable max auto-forward port (default: 10000)
19
+
20
+ ## Installation
21
+
22
+ ### With uv (recommended):
23
+
24
+ ```bash
25
+ uvx ssh-auto-forward hetzner
26
+ ```
27
+
28
+ ### Install with pip:
29
+
30
+ ```bash
31
+ pip install ssh-auto-forward
32
+ ```
33
+
34
+ ### Install locally:
35
+
36
+ ```bash
37
+ cd portforwards
38
+ uv sync
39
+ ```
40
+
41
+ ## Running
42
+
43
+ ### Dashboard mode (default):
44
+
45
+ ```bash
46
+ ssh-auto-forward hetzner
47
+ ```
48
+
49
+ ### CLI mode (headless):
50
+
51
+ ```bash
52
+ ssh-auto-forward hetzner --cli
53
+ ```
54
+
55
+ ### With uvx (no installation):
56
+
57
+ ```bash
58
+ uvx ssh-auto-forward hetzner
59
+ ```
60
+
61
+ ## Dashboard Controls
62
+
63
+ | Key | Action |
64
+ |-----|--------|
65
+ | X / Enter | Toggle port (open if closed, close if opened) |
66
+ | O | Open URL in browser (for forwarded ports) |
67
+ | R | Refresh port list |
68
+ | L | Toggle log panel |
69
+ | Q | Quit |
70
+
71
+ ## Options
72
+
73
+ ```
74
+ -v, --verbose Enable verbose logging
75
+ -i, --interval SECS Scan interval in seconds (default: 5)
76
+ -p, --port-range MIN:MAX Local port range for remapping (default: 3000:10000)
77
+ -s, --skip PORTS Comma-separated ports to skip (default: all ports < 1000)
78
+ -c, --config PATH Path to SSH config file
79
+ -m, --max-auto-port PORT Maximum port to auto-forward (default: 10000)
80
+ --include-configs Include ports already forwarded via SSH config LocalForward
81
+ --cli Run in CLI mode instead of dashboard
82
+ --version Show version and exit
83
+ ```
84
+
85
+ ## Examples
86
+
87
+ ```bash
88
+ # Dashboard mode (default)
89
+ ssh-auto-forward hetzner
90
+
91
+ # CLI mode
92
+ ssh-auto-forward hetzner --cli
93
+
94
+ # Scan every 3 seconds
95
+ ssh-auto-forward hetzner -i 3
96
+
97
+ # Use specific port range
98
+ ssh-auto-forward hetzner -p 4000:9000
99
+
100
+ # Skip specific ports
101
+ ssh-auto-forward hetzner -s 22,80,443
102
+
103
+ # Verbose mode
104
+ ssh-auto-forward hetzner -v
105
+
106
+ # Only auto-forward ports up to 5000 (higher ports shown but not auto-forwarded)
107
+ ssh-auto-forward hetzner -m 5000
108
+
109
+ # Include ports already forwarded via SSH config LocalForward
110
+ ssh-auto-forward hetzner --include-configs
111
+ ```
112
+
113
+ ## Performance
114
+
115
+ Throughput is 150-190 MB/s for large file transfers (45-70% of native `ssh -L`). HTML page latency is 2-3 ms median at up to 10 req/s with zero failures. The overhead comes from paramiko (Python) vs OpenSSH (C) - negligible for interactive use like web browsing.
116
+
117
+ See [benchmarks/](benchmarks/) for full methodology and results.
118
+
119
+ ## How it works
120
+
121
+ 1. Connects to your remote server using your SSH config
122
+ 2. Runs `ss -tlnp` on the remote to find listening ports
123
+ 3. Creates SSH tunnels for each discovered port
124
+ 4. Continuously monitors for new/closed ports
125
+ 5. Handles port conflicts on your local machine
126
+
127
+ ### SSH Config Integration
128
+
129
+ The tool reads your SSH config file (`~/.ssh/config`) for connection details. It also respects `LocalForward` directives:
130
+
131
+ ```ssh
132
+ Host myserver
133
+ HostName example.com
134
+ User myuser
135
+ LocalForward 8080 localhost:8080 # This port is excluded by default
136
+ LocalForward 3000 localhost:3000
137
+ ```
138
+
139
+ By default, ports that are already forwarded via `LocalForward` are **excluded** from the dashboard list since they're handled by SSH itself. Use `--include-configs` to show them.
140
+
141
+ ## Status messages
142
+
143
+ ```
144
+ ✓ Connected!
145
+ ✓ Forwarding port 2999 (python3)
146
+ ✓ Forwarding port 7681 (ttyd)
147
+ ✓ Forwarding remote port 19840 -> local port 3000 (node)
148
+ ✗ Remote port 2999 is no longer listening, stopping tunnel
149
+ ```
150
+
151
+ ## Testing
152
+
153
+ Start a test server on your remote machine:
154
+
155
+ ```bash
156
+ ssh hetzner "python3 -m http.server 9999 --bind 127.0.0.1 &"
157
+ ```
158
+
159
+ Then run `ssh-auto-forward hetzner` and access it locally:
160
+
161
+ ```bash
162
+ curl http://localhost:9999/
163
+ ```
164
+
165
+ ## Stopping
166
+
167
+ - Dashboard: Press `Q`
168
+ - CLI mode: Press `Ctrl+C`
169
+
170
+ ## Requirements
171
+
172
+ - Python 3.10+
173
+ - paramiko
174
+ - textual (for TUI dashboard)
175
+ - Remote server must have `ss` or `netstat` command available
176
+
177
+ ## Tests
178
+
179
+ ### Unit tests (run locally, no SSH required):
180
+
181
+ ```bash
182
+ uv run pytest tests/ -v
183
+ ```
184
+
185
+ ### Integration tests (Docker, default):
186
+
187
+ ```bash
188
+ uv run pytest tests_integration/ -v
189
+ ```
190
+
191
+ This starts a Docker container with SSH server and tests against it.
192
+
193
+ ### Integration tests (real SSH server):
194
+
195
+ ```bash
196
+ SSH_AUTO_FORWARD_TEST_HOST=your-server uv run pytest tests_integration/ -v
197
+ ```
@@ -0,0 +1,139 @@
1
+ # Benchmarks
2
+
3
+ Measures the throughput and latency of ssh-auto-forward compared to native `ssh -L` port forwarding.
4
+
5
+ ## Architecture
6
+
7
+ The benchmarks use three separate processes to mirror real-world usage:
8
+
9
+ ```
10
+ Process 1: Docker container Process 2: Tunnel Process 3: Client
11
+ +--------------------------+ +---------------------+ +------------------+
12
+ | SSH server (port 22) |<-------->| ssh-auto-forward | | Python HTTP |
13
+ | nginx (port 8081) | SSH | --cli | | client |
14
+ | darkhttpd (port 8082) | tunnel | (or native ssh -L) |<------>| (urllib) |
15
+ | python http (port 8080) | +---------------------+ +------------------+
16
+ | stream gen (port 8083) | binds local ports downloads through
17
+ +--------------------------+ 8080-8083 localhost tunnel
18
+ Only port 22 exposed
19
+ ```
20
+
21
+ Only SSH port 22 is exposed from the Docker container. All HTTP traffic goes through the SSH tunnel, just like a real user would experience.
22
+
23
+ - Process 1 (Docker): Runs `panubo/sshd` with nginx, darkhttpd, python http.server, and a streaming generator
24
+ - Process 2 (Tunnel): Either `ssh-auto-forward --cli` (our tool with dashboard) or native `ssh -L` (baseline)
25
+ - Process 3 (Client): Python `urllib` making HTTP requests through the tunnel, like a browser would
26
+
27
+ ## Test Data
28
+
29
+ | File | Size | Source |
30
+ |------|------|--------|
31
+ | `10mb.bin` | 10 MB | Pre-generated on disk (`dd if=/dev/urandom`) |
32
+ | `100mb.bin` | 100 MB | Pre-generated on disk |
33
+ | `1gb.bin` | 1 GB | Pre-generated on disk |
34
+ | 10 GB stream | 10 GB | Generated on the fly by `stream_generator.py` |
35
+ | `small/page_*.html` | ~10 KB each, 1000 files | Pre-generated HTML with random content |
36
+
37
+ ## Benchmarks
38
+
39
+ ### 1. Large File Downloads (`bench_large_files.py`)
40
+
41
+ Downloads pre-generated files (10 MB, 100 MB, 1 GB) and a 10 GB on-the-fly stream through each HTTP server.
42
+
43
+ One ssh-auto-forward tunnel stays open for all tests to match real usage (the user starts the dashboard once and uses it for all downloads).
44
+
45
+ | Server | File | ssh-auto-forward | native ssh -L |
46
+ |--------|------|-----------------|---------------|
47
+ | nginx | 10 MB | 121.7 MB/s | 164.4 MB/s |
48
+ | nginx | 100 MB | 158.1 MB/s | 287.1 MB/s |
49
+ | nginx | 1 GB | 141.6 MB/s | 269.9 MB/s |
50
+ | darkhttpd | 10 MB | 106.1 MB/s | 235.3 MB/s |
51
+ | darkhttpd | 100 MB | 192.8 MB/s | 299.4 MB/s |
52
+ | darkhttpd | 1 GB | 152.6 MB/s | 316.9 MB/s |
53
+ | python | 10 MB | 131.4 MB/s | 191.8 MB/s |
54
+ | python | 100 MB | 192.9 MB/s | 353.0 MB/s |
55
+ | python | 1 GB | 149.9 MB/s | 383.9 MB/s |
56
+
57
+ 10 GB streaming:
58
+
59
+ | Tunnel | Throughput | Time |
60
+ |--------|-----------|------|
61
+ | ssh-auto-forward | 151.6 MB/s | 67.6s |
62
+ | native ssh -L | 220.7 MB/s | 46.4s |
63
+
64
+ ssh-auto-forward achieves 45-70% of native ssh throughput for large transfers. The overhead comes from paramiko's Python-based SSH implementation vs OpenSSH's C implementation.
65
+
66
+ ### 2. HTML Stress Test (`bench_html_stress.py`)
67
+
68
+ Fires HTTP requests for ~10 KB HTML pages at a controlled rate. Tests whether the tunnel can sustain the target request rate without failures.
69
+
70
+ Realistic browsing (1 req/s, 30 requests):
71
+
72
+ | Server | Tunnel | Actual RPS | p50 | p95 | p99 | Failed |
73
+ |--------|--------|-----------|-----|-----|-----|--------|
74
+ | nginx | ssh-auto-fwd | 1.0 r/s | 3 ms | 18 ms | 25 ms | 0 |
75
+ | darkhttpd | ssh-auto-fwd | 1.0 r/s | 3 ms | 7 ms | 20 ms | 0 |
76
+ | python | ssh-auto-fwd | 1.0 r/s | 3 ms | 4 ms | 19 ms | 0 |
77
+ | nginx | native ssh | 1.0 r/s | 2 ms | 2 ms | 3 ms | 0 |
78
+ | darkhttpd | native ssh | 1.0 r/s | 2 ms | 2 ms | 3 ms | 0 |
79
+ | python | native ssh | 1.0 r/s | 2 ms | 3 ms | 4 ms | 0 |
80
+
81
+ Stress test (10 req/s, 100 requests):
82
+
83
+ | Server | Tunnel | Actual RPS | p50 | p95 | p99 | Failed |
84
+ |--------|--------|-----------|-----|-----|-----|--------|
85
+ | nginx | ssh-auto-fwd | 10.1 r/s | 3 ms | 4 ms | 34 ms | 0 |
86
+ | darkhttpd | ssh-auto-fwd | 10.1 r/s | 2 ms | 5 ms | 21 ms | 0 |
87
+ | python | ssh-auto-fwd | 10.1 r/s | 3 ms | 9 ms | 38 ms | 0 |
88
+ | nginx | native ssh | 10.1 r/s | 2 ms | 2 ms | 2 ms | 0 |
89
+ | darkhttpd | native ssh | 10.1 r/s | 1 ms | 2 ms | 2 ms | 0 |
90
+ | python | native ssh | 10.1 r/s | 2 ms | 2 ms | 3 ms | 0 |
91
+
92
+ Both tunnels handle 10 req/s with zero failures. ssh-auto-forward adds ~1 ms median latency overhead. Tail latency (p99) is higher at ~20-38 ms vs ~2-3 ms for native ssh, likely due to paramiko's Python event loop.
93
+
94
+ ### 3. Buffer Size Sweep (`bench_buffer_sweep.py`)
95
+
96
+ Tests the impact of the internal pipe buffer size on throughput (100 MB file through nginx).
97
+
98
+ | Buffer Size | Throughput | Time |
99
+ |------------|-----------|------|
100
+ | 4,096 bytes | 65.1 MB/s | 1.53s |
101
+ | 16,384 bytes | 151.9 MB/s | 0.66s |
102
+ | 65,536 bytes | 179.6 MB/s | 0.56s |
103
+
104
+ Increasing the buffer from 4 KB to 64 KB gives a 2.8x throughput improvement. The default was changed from 4096 to 65536 as part of the forwarder fix.
105
+
106
+ ## Running
107
+
108
+ Prerequisites: Docker, SSH key in `~/.ssh/`
109
+
110
+ ```bash
111
+ # Run all benchmarks
112
+ uv run python benchmarks/bench_large_files.py
113
+ uv run python benchmarks/bench_html_stress.py
114
+ uv run python benchmarks/bench_buffer_sweep.py
115
+ ```
116
+
117
+ Results are saved as JSON in `benchmarks/results_*.json`.
118
+
119
+ ## Files
120
+
121
+ | File | Purpose |
122
+ |------|---------|
123
+ | `bench_large_files.py` | Large file download benchmark (10 MB - 10 GB) |
124
+ | `bench_html_stress.py` | HTML page stress test (1 and 10 req/s) |
125
+ | `bench_buffer_sweep.py` | Buffer size comparison (4 KB, 16 KB, 64 KB) |
126
+ | `bench_common.py` | Shared infrastructure: Docker, tunnels, download clients |
127
+ | `tunnel_helper.py` | Subprocess that runs the ssh-auto-forward CLI or direct SSHTunnel |
128
+ | `download_helper.py` | Subprocess that downloads files (single, many, stress modes) |
129
+ | `Dockerfile` | Docker image with SSH + HTTP servers and test data |
130
+ | `nginx-benchmark.conf` | nginx configuration for benchmark serving |
131
+ | `stream_generator.py` | HTTP server that generates large responses on the fly |
132
+
133
+ ## Key Findings
134
+
135
+ 1. ssh-auto-forward works correctly -zero failures across all tests, no truncated responses (after the `sendall` fix)
136
+ 2. Throughput is 45-70% of native ssh for bulk transfers -the overhead is paramiko (Python) vs OpenSSH (C)
137
+ 3. Latency is excellent for interactive use -2-3 ms median for HTML page requests
138
+ 4. Buffer size matters -increasing from 4 KB to 64 KB gave 2.8x throughput improvement
139
+ 5. Handles 10 req/s stress test with zero failures -more than sufficient for web browsing use cases
@@ -0,0 +1,68 @@
1
+ # Remote Benchmark Results
2
+
3
+ ## Host
4
+ - Remote: hetzner (Hetzner VPS)
5
+ - Local: Windows (MinGW64)
6
+
7
+ ## Native SSH Performance (baseline)
8
+
9
+ | File | Throughput | Time |
10
+ |------|------------|------|
11
+ | 100KB | ~10 MB/s | ~0.01s |
12
+ | 500KB | ~11 MB/s | ~0.04s |
13
+ | 1MB | ~12 MB/s | ~0.08s |
14
+ | 5MB | ~12 MB/s | ~0.4s |
15
+ | 10MB | ~12 MB/s | ~0.8s |
16
+ | 50MB | ~11 MB/s | ~4.5s |
17
+ | 100MB | ~12 MB/s | ~8.5s |
18
+
19
+ Native SSH `-L` works perfectly and consistently across all file sizes.
20
+
21
+ ## ssh-auto-forward Performance (After Fix)
22
+
23
+ | File | Throughput | Time | Status |
24
+ |------|------------|------|--------|
25
+ | 100KB | ~10 MB/s | ~0.01s | Fixed |
26
+ | 500KB | ~11 MB/s | ~0.04s | Fixed |
27
+ | 1MB | ~12 MB/s | ~0.08s | Fixed |
28
+ | 5MB | ~12 MB/s | ~0.4s | Fixed |
29
+ | 10MB | ~11.6 MB/s | ~0.9s | Fixed |
30
+ | 50MB | ~11.5 MB/s | ~4.4s | Fixed |
31
+ | 100MB | ~11.8 MB/s | ~8.6s | Fixed |
32
+
33
+ ## The Fix
34
+
35
+ The Windows performance issue was fixed by using `select.select()` with Paramiko channel's `fileno()` method:
36
+
37
+ ```python
38
+ # Get the channel's file descriptor (works on all platforms)
39
+ chan_fd = chan.fileno()
40
+
41
+ # Use select to wait for data on either socket or channel
42
+ rlist, _, _ = select.select([sock, chan_fd], [], [], timeout)
43
+ ```
44
+
45
+ This approach:
46
+ - Works cross-platform (Windows ARM64, AMD64, Linux)
47
+ - Achieves ~98% of native SSH performance
48
+ - Eliminates the polling overhead that caused the 200x slowdown
49
+
50
+ ## Previous Windows Performance Limitation (RESOLVED)
51
+
52
+ ssh-auto-forward on Windows previously experienced significant performance degradation (~200x slower than native SSH) due to:
53
+
54
+ 1. `select.select()` limitation on Windows: Python's `select` module on Windows only works with sockets, not with Paramiko SSH channel objects directly.
55
+
56
+ 2. Previous workaround overhead: The old implementation used polling (`select` on socket + `chan.recv_ready()` for channel), which added significant latency.
57
+
58
+ 3. Solution: Paramiko channels provide a `fileno()` method that returns a valid file descriptor that works with `select()` on all platforms.
59
+
60
+ ## Recommendation
61
+
62
+ The Windows performance limitation is now RESOLVED. ssh-auto-forward performs comparably to native SSH on all platforms:
63
+
64
+ - Windows ARM64: ~11-12 MB/s (98% of native SSH)
65
+ - Windows AMD64: ~11-12 MB/s (98% of native SSH)
66
+ - Linux: Similar performance (uses same code path)
67
+
68
+ The tool is now recommended for all platforms for its automatic port detection and dashboard features.