0
# Port Forwarding
1
2
Comprehensive port forwarding capabilities for creating secure tunnels through SSH connections, including local forwarding, remote forwarding, and Unix domain socket support.
3
4
## Capabilities
5
6
### Local Port Forwarding
7
8
Forward connections from client through server to destination (client initiates connections).
9
10
```javascript { .api }
11
/**
12
* Create local port forwarding connection (client -> server -> destination)
13
* @param srcIP - Source IP address (usually from client perspective)
14
* @param srcPort - Source port number
15
* @param dstIP - Destination IP address (from server perspective)
16
* @param dstPort - Destination port number
17
* @param callback - Callback receiving connection stream
18
* @returns false if connection not ready, true otherwise
19
*/
20
forwardOut(srcIP: string, srcPort: number, dstIP: string, dstPort: number, callback: ChannelCallback): boolean;
21
22
type ChannelCallback = (err: Error | null, stream?: ClientChannel) => void;
23
```
24
25
**Local Forwarding Examples:**
26
27
```javascript
28
const { Client } = require('ssh2');
29
30
const conn = new Client();
31
conn.on('ready', () => {
32
console.log('Client :: ready');
33
34
// Forward connection to database through SSH server
35
conn.forwardOut('127.0.0.1', 0, 'database.internal', 5432, (err, stream) => {
36
if (err) throw err;
37
38
console.log('Connected to database through SSH tunnel');
39
40
// Use stream as regular TCP connection
41
stream.on('data', (data) => {
42
console.log('Received:', data.toString());
43
});
44
45
stream.write('SELECT version();\n');
46
});
47
48
}).connect({
49
host: 'jump-server.com',
50
username: 'user',
51
privateKey: privateKey
52
});
53
```
54
55
### Remote Port Forwarding
56
57
Set up server to listen on port and forward connections to client (server accepts connections).
58
59
```javascript { .api }
60
/**
61
* Request remote port forwarding (server binds port and forwards to client)
62
* Server will listen on bindAddr:bindPort and forward connections to client
63
* @param bindAddr - Address for server to bind ('' for all interfaces)
64
* @param bindPort - Port for server to bind (0 for dynamic allocation)
65
* @param callback - Callback with actual bound port
66
* @returns false if connection not ready, true otherwise
67
*/
68
forwardIn(bindAddr: string, bindPort: number, callback: ForwardCallback): boolean;
69
70
/**
71
* Cancel remote port forwarding
72
* @param bindAddr - Address that was bound
73
* @param bindPort - Port that was bound
74
* @param callback - Callback with cancellation result
75
* @returns false if connection not ready, true otherwise
76
*/
77
unforwardIn(bindAddr: string, bindPort: number, callback: UnforwardCallback): boolean;
78
79
type ForwardCallback = (err: Error | null, bindPort?: number) => void;
80
type UnforwardCallback = (err: Error | null) => void;
81
```
82
83
**Remote Forwarding Examples:**
84
85
```javascript
86
const { Client } = require('ssh2');
87
88
const conn = new Client();
89
conn.on('ready', () => {
90
console.log('Client :: ready');
91
92
// Request server to listen on port 8080 and forward to client
93
conn.forwardIn('', 8080, (err, bindPort) => {
94
if (err) throw err;
95
96
console.log(`Server listening on port ${bindPort}`);
97
console.log('Remote connections will be forwarded to client');
98
});
99
100
}).on('tcp connection', (details, accept, reject) => {
101
console.log('Incoming connection:', details);
102
103
// Accept the forwarded connection
104
const stream = accept();
105
106
// Handle the connection (e.g., proxy to local service)
107
const net = require('net');
108
const localConnection = net.createConnection(3000, 'localhost');
109
110
stream.pipe(localConnection);
111
localConnection.pipe(stream);
112
113
stream.on('close', () => {
114
console.log('Forwarded connection closed');
115
localConnection.end();
116
});
117
118
}).connect({
119
host: 'public-server.com',
120
username: 'user',
121
privateKey: privateKey
122
});
123
```
124
125
### Unix Domain Socket Forwarding (OpenSSH Extensions)
126
127
Forward Unix domain sockets for local IPC communication.
128
129
```javascript { .api }
130
/**
131
* Request Unix domain socket forwarding (OpenSSH extension)
132
* Server will listen on Unix socket and forward connections to client
133
* @param socketPath - Unix socket path on server
134
* @param callback - Callback with operation result
135
* @returns false if connection not ready, true otherwise
136
*/
137
openssh_forwardInStreamLocal(socketPath: string, callback: ForwardCallback): boolean;
138
139
/**
140
* Cancel Unix domain socket forwarding (OpenSSH extension)
141
* @param socketPath - Unix socket path to unbind
142
* @param callback - Callback with cancellation result
143
* @returns false if connection not ready, true otherwise
144
*/
145
openssh_unforwardInStreamLocal(socketPath: string, callback: UnforwardCallback): boolean;
146
147
/**
148
* Connect to Unix domain socket through server (OpenSSH extension)
149
* @param socketPath - Unix socket path on server to connect to
150
* @param callback - Callback receiving connection stream
151
* @returns false if connection not ready, true otherwise
152
*/
153
openssh_forwardOutStreamLocal(socketPath: string, callback: ChannelCallback): boolean;
154
```
155
156
**Unix Socket Forwarding Examples:**
157
158
```javascript
159
const { Client } = require('ssh2');
160
161
const conn = new Client();
162
conn.on('ready', () => {
163
console.log('Client :: ready');
164
165
// Forward Unix socket from server to client
166
conn.openssh_forwardInStreamLocal('/tmp/remote-socket', (err) => {
167
if (err) throw err;
168
console.log('Unix socket forwarding established');
169
});
170
171
// Connect to Unix socket on server
172
conn.openssh_forwardOutStreamLocal('/var/run/docker.sock', (err, stream) => {
173
if (err) throw err;
174
console.log('Connected to Docker socket through SSH');
175
176
// Use stream to communicate with Docker daemon
177
stream.write('GET /containers/json HTTP/1.1\r\nHost: localhost\r\n\r\n');
178
});
179
180
}).on('unix connection', (info, accept, reject) => {
181
console.log('Incoming Unix connection:', info.socketPath);
182
183
const stream = accept();
184
// Handle Unix socket connection...
185
186
}).connect({
187
host: 'docker-host.com',
188
username: 'user',
189
privateKey: privateKey
190
});
191
```
192
193
## Server-Side Port Forwarding
194
195
Handle port forwarding requests when operating as SSH server.
196
197
### Connection Request Events
198
199
```javascript { .api }
200
/**
201
* Handle direct TCP/IP connection requests (local forwarding from client)
202
*/
203
client.on('tcpip', (accept, reject, info: TcpipInfo) => {
204
console.log(`Client wants to connect to ${info.destIP}:${info.destPort}`);
205
206
if (allowConnection(info)) {
207
const stream = accept();
208
// Set up connection to destination
209
const net = require('net');
210
const connection = net.createConnection(info.destPort, info.destIP);
211
stream.pipe(connection);
212
connection.pipe(stream);
213
} else {
214
reject();
215
}
216
});
217
218
/**
219
* Handle OpenSSH streamlocal connection requests (Unix socket forwarding)
220
*/
221
client.on('openssh.streamlocal', (accept, reject, info: StreamLocalInfo) => {
222
console.log(`Client wants to connect to Unix socket: ${info.socketPath}`);
223
224
const stream = accept();
225
const net = require('net');
226
const connection = net.createConnection(info.socketPath);
227
stream.pipe(connection);
228
connection.pipe(stream);
229
});
230
231
interface TcpipInfo {
232
srcIP: string;
233
srcPort: number;
234
destIP: string;
235
destPort: number;
236
}
237
238
interface StreamLocalInfo {
239
socketPath: string;
240
}
241
```
242
243
### Global Request Handling
244
245
```javascript { .api }
246
/**
247
* Handle global requests for port forwarding setup
248
*/
249
client.on('request', (accept, reject, name: string, info: RequestInfo) => {
250
console.log(`Global request: ${name}`, info);
251
252
switch (name) {
253
case 'tcpip-forward':
254
// Set up remote port forwarding
255
console.log(`Binding ${info.bindAddr}:${info.bindPort}`);
256
const server = net.createServer((connection) => {
257
// Forward incoming connections to client
258
client.forwardOut(
259
info.bindAddr, info.bindPort,
260
connection.remoteAddress, connection.remotePort,
261
(err, stream) => {
262
if (!err) {
263
connection.pipe(stream);
264
stream.pipe(connection);
265
} else {
266
connection.end();
267
}
268
}
269
);
270
});
271
272
server.listen(info.bindPort, info.bindAddr, () => {
273
accept(); // Accept the forwarding request
274
});
275
break;
276
277
case 'cancel-tcpip-forward':
278
// Cancel remote port forwarding
279
console.log(`Unbinding ${info.bindAddr}:${info.bindPort}`);
280
// Clean up server listening on this port
281
accept();
282
break;
283
284
case 'streamlocal-forward@openssh.com':
285
// Set up Unix socket forwarding
286
console.log(`Binding Unix socket: ${info.socketPath}`);
287
accept();
288
break;
289
290
default:
291
reject();
292
}
293
});
294
295
interface RequestInfo {
296
bindAddr?: string;
297
bindPort?: number;
298
socketPath?: string;
299
[key: string]: any;
300
}
301
```
302
303
## Advanced Port Forwarding Patterns
304
305
### Dynamic Port Forwarding (SOCKS Proxy)
306
307
Create SOCKS proxy functionality using port forwarding.
308
309
```javascript
310
const { Client } = require('ssh2');
311
const net = require('net');
312
313
function createSOCKSProxy(sshConfig, localPort) {
314
const sshClient = new Client();
315
316
// Create local SOCKS server
317
const socksServer = net.createServer((clientSocket) => {
318
let stage = 0;
319
let targetHost, targetPort;
320
321
clientSocket.on('data', (data) => {
322
if (stage === 0) {
323
// SOCKS handshake
324
if (data[0] === 0x05) {
325
clientSocket.write(Buffer.from([0x05, 0x00])); // No auth required
326
stage = 1;
327
}
328
} else if (stage === 1) {
329
// SOCKS connect request
330
if (data[0] === 0x05 && data[1] === 0x01) {
331
const addrType = data[3];
332
let offset = 4;
333
334
if (addrType === 0x01) {
335
// IPv4
336
targetHost = `${data[4]}.${data[5]}.${data[6]}.${data[7]}`;
337
offset = 8;
338
} else if (addrType === 0x03) {
339
// Domain name
340
const domainLen = data[4];
341
targetHost = data.slice(5, 5 + domainLen).toString();
342
offset = 5 + domainLen;
343
}
344
345
targetPort = data.readUInt16BE(offset);
346
347
// Forward through SSH
348
sshClient.forwardOut('127.0.0.1', 0, targetHost, targetPort, (err, stream) => {
349
if (err) {
350
clientSocket.write(Buffer.from([0x05, 0x01])); // General failure
351
clientSocket.end();
352
} else {
353
clientSocket.write(Buffer.from([0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0])); // Success
354
clientSocket.pipe(stream);
355
stream.pipe(clientSocket);
356
}
357
});
358
}
359
}
360
});
361
});
362
363
sshClient.on('ready', () => {
364
socksServer.listen(localPort, () => {
365
console.log(`SOCKS proxy listening on port ${localPort}`);
366
});
367
}).connect(sshConfig);
368
369
return { sshClient, socksServer };
370
}
371
372
// Usage
373
const proxy = createSOCKSProxy({
374
host: 'ssh-server.com',
375
username: 'user',
376
privateKey: privateKey
377
}, 1080);
378
```
379
380
### Multi-Hop Port Forwarding
381
382
Chain multiple SSH connections for complex routing.
383
384
```javascript
385
const { Client } = require('ssh2');
386
387
function createMultiHopTunnel() {
388
const jump1 = new Client();
389
const jump2 = new Client();
390
391
jump1.on('ready', () => {
392
console.log('Jump1 connected');
393
394
// Connect through first jump server to second
395
jump1.forwardOut('127.0.0.1', 0, 'internal-jump.corp', 22, (err, stream) => {
396
if (err) throw err;
397
398
// Use first tunnel as transport for second SSH connection
399
jump2.connect({
400
username: 'user2',
401
privateKey: privateKey2,
402
sock: stream
403
});
404
});
405
406
}).connect({
407
host: 'external-jump.com',
408
username: 'user1',
409
privateKey: privateKey1
410
});
411
412
jump2.on('ready', () => {
413
console.log('Jump2 connected through Jump1');
414
415
// Now forward to final destination through both jumps
416
jump2.forwardOut('127.0.0.1', 0, 'secure-server.corp', 3389, (err, stream) => {
417
if (err) throw err;
418
419
console.log('Connected to final destination through multi-hop tunnel');
420
// Use stream for RDP connection or other protocol
421
});
422
});
423
}
424
```
425
426
## Connection Event Details
427
428
### TCP Connection Events
429
430
```javascript { .api }
431
interface TcpConnectionDetails {
432
srcIP: string; // Source IP address
433
srcPort: number; // Source port number
434
destIP: string; // Destination IP address
435
destPort: number; // Destination port number
436
}
437
438
// Client event for incoming forwarded connections
439
client.on('tcp connection', (details: TcpConnectionDetails, accept: AcceptConnection, reject: RejectConnection) => {
440
console.log(`Incoming TCP connection from ${details.srcIP}:${details.srcPort} to ${details.destIP}:${details.destPort}`);
441
442
const stream = accept();
443
// Handle the forwarded connection...
444
});
445
```
446
447
### Unix Connection Events
448
449
```javascript { .api }
450
interface UnixConnectionInfo {
451
socketPath: string; // Unix socket path
452
}
453
454
// Client event for incoming Unix socket connections
455
client.on('unix connection', (info: UnixConnectionInfo, accept: AcceptConnection, reject: RejectConnection) => {
456
console.log(`Incoming Unix connection to ${info.socketPath}`);
457
458
const stream = accept();
459
// Handle the Unix socket connection...
460
});
461
```
462
463
## Type Definitions
464
465
### Connection Handling Types
466
467
```javascript { .api }
468
type AcceptConnection<T = ClientChannel> = () => T;
469
type RejectConnection = () => boolean;
470
471
interface ClientChannel extends Duplex {
472
stderr: Duplex;
473
setWindow(rows: number, cols: number, height?: number, width?: number): boolean;
474
signal(signalName: string): boolean;
475
exit(status: number): boolean;
476
}
477
```
478
479
## Best Practices
480
481
### Security Considerations
482
483
```javascript
484
// Restrict forwarding destinations
485
client.on('tcpip', (accept, reject, info) => {
486
const allowedHosts = ['127.0.0.1', 'localhost', '192.168.1.0/24'];
487
const allowedPorts = [80, 443, 3000, 8080];
488
489
if (isAllowedDestination(info.destIP, allowedHosts) &&
490
allowedPorts.includes(info.destPort)) {
491
accept();
492
} else {
493
console.log(`Rejected connection to ${info.destIP}:${info.destPort}`);
494
reject();
495
}
496
});
497
498
// Rate limiting
499
const connectionCounts = new Map();
500
501
client.on('tcpip', (accept, reject, info) => {
502
const key = `${info.srcIP}:${info.srcPort}`;
503
const count = connectionCounts.get(key) || 0;
504
505
if (count > 10) {
506
console.log(`Rate limit exceeded for ${key}`);
507
reject();
508
} else {
509
connectionCounts.set(key, count + 1);
510
const stream = accept();
511
512
stream.on('close', () => {
513
connectionCounts.set(key, Math.max(0, connectionCounts.get(key) - 1));
514
});
515
}
516
});
517
```
518
519
### Connection Management
520
521
```javascript
522
// Track active forwards
523
const activeForwards = new Map();
524
525
function createForward(remoteAddr, remotePort, localAddr, localPort) {
526
const key = `${remoteAddr}:${remotePort}`;
527
528
if (activeForwards.has(key)) {
529
console.log(`Forward already exists: ${key}`);
530
return;
531
}
532
533
conn.forwardIn(remoteAddr, remotePort, (err, bindPort) => {
534
if (err) {
535
console.error(`Failed to create forward: ${err.message}`);
536
} else {
537
activeForwards.set(key, { bindPort, localAddr, localPort });
538
console.log(`Forward created: ${remoteAddr}:${bindPort} -> ${localAddr}:${localPort}`);
539
}
540
});
541
}
542
543
function removeForward(remoteAddr, remotePort) {
544
const key = `${remoteAddr}:${remotePort}`;
545
const forward = activeForwards.get(key);
546
547
if (forward) {
548
conn.unforwardIn(remoteAddr, forward.bindPort, (err) => {
549
if (!err) {
550
activeForwards.delete(key);
551
console.log(`Forward removed: ${key}`);
552
}
553
});
554
}
555
}
556
```