-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwebsockify.js
157 lines (134 loc) · 4.19 KB
/
websockify.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#!/usr/bin/env node
const fs = require('fs');
const http = require('http');
const https = require('https');
const net = require('net');
const path = require('path');
const WebSocket = require('ws');
const yargs = require('yargs');
const argv = yargs
.scriptName('websockify')
.usage('$0 <port> <target>', false, (yargs) => {
yargs
.positional('port', {
describe: 'WebSocket server port',
type: 'number',
})
.positional('target', {
describe: 'TCP server port, host:port, or path to JSON file with ID-to-TCP-port mappings',
type: 'string',
});
})
.option('cert', {
alias: 'c',
describe: 'Path to SSL certificate file (for WSS)',
type: 'string',
})
.option('key', {
alias: 'k',
describe: 'Path to SSL key file (for WSS)',
type: 'string',
})
.option('debug', {
alias: 'd',
describe: 'Enable debugging mode',
type: 'boolean',
})
.alias('version', 'v')
.help(false)
.argv;
const { port, target, cert, key, debug: isDebug } = argv;
let tcpPortMappings = {};
let targetHost = '127.0.0.1';
let targetPort = null;
if (target.includes(':')) {
const [host, portStr] = target.split(':');
targetHost = host;
targetPort = parseInt(portStr, 10);
if (isNaN(targetPort)) {
console.error('Invalid target port. It should be a valid number.');
process.exit(1);
}
} else if (!isNaN(target)) {
targetPort = parseInt(target, 10);
if (isNaN(targetPort)) {
console.error('Invalid target port. It should be a valid number.');
process.exit(1);
}
} else {
try {
const filePath = path.resolve(target);
tcpPortMappings = JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch (error) {
console.error(`Error reading or parsing target file: ${error.message}`);
process.exit(1);
}
}
const debug = (connectionId, ...args) => {
if (isDebug) {
console.log(`[${connectionId}]`, ...args);
}
};
let server;
if (cert && key) {
try {
const httpsOptions = {
cert: fs.readFileSync(cert),
key: fs.readFileSync(key),
};
server = https.createServer(httpsOptions);
} catch (error) {
console.error(`Error reading certificate or key files: ${error.message}`);
process.exit(1);
}
} else {
server = http.createServer();
}
const wss = new WebSocket.Server({ server });
let connectionCounter = 0;
wss.on('error', (err) => {
console.error(`WebSocket server error: ${err.message}`);
});
wss.on('connection', (ws, req) => {
const urlParts = req.url.split('/');
const id = urlParts[1];
const currentTargetPort = id && tcpPortMappings[id] ? tcpPortMappings[id] : targetPort;
if (!currentTargetPort) {
ws.close(4000, 'Invalid ID');
return;
}
const connectionId = `WS#${++connectionCounter} ${req.socket.remoteAddress}`;
const tcpSocket = net.connect(currentTargetPort, targetHost, () => {
debug(connectionId, 'TCP connection opened');
});
ws.on('message', (message) => {
debug(connectionId, 'Received from WebSocket:', message.toString().trim());
tcpSocket.write(message);
});
ws.on('error', (err) => {
console.error(`[${connectionId}] WebSocket error: ${err.message}`);
ws.close();
});
ws.on('close', (code, reason) => {
debug(connectionId, `WebSocket closed with code: ${code}, reason: ${Buffer.isBuffer(reason) ? reason.toString('utf8') : reason}`);
tcpSocket.end();
});
tcpSocket.on('data', (data) => {
debug(connectionId, 'Received from TCP:', data.toString().trim());
ws.send(data);
});
tcpSocket.on('error', (err) => {
console.error(`[${connectionId}] TCP connection error: ${err.message}`);
ws.close();
});
tcpSocket.on('close', () => {
debug(connectionId, 'TCP connection closed');
ws.close();
});
});
server.listen(port, () => {
const { address, port: serverPort } = server.address();
console.log(`Proxying WebSocket connections from [${address}]:${serverPort} ${targetPort ? `to [${targetHost}]:${targetPort}` : `using mappings from file: ${target}`}`);
console.log(cert && key ? `Running in secured WebSocket (wss://) mode\nSSL certificate: ${cert}\nSSL key: ${key}` : 'Running in unsecured WebSocket (ws://) mode');
console.log(`Debug mode is ${isDebug ? 'enabled' : 'disabled'}`);
});