@bbk47/toolbox 1.0.7 → 2.0.0
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.
- package/lib/deferred.d.ts +7 -0
- package/lib/deferred.d.ts.map +1 -0
- package/lib/deferred.js +8 -4
- package/lib/deferred.js.map +1 -0
- package/lib/encrypt.d.ts +19 -0
- package/lib/encrypt.d.ts.map +1 -0
- package/lib/encrypt.js +78 -79
- package/lib/encrypt.js.map +1 -0
- package/lib/index.d.ts +19 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +58 -8
- package/lib/index.js.map +1 -0
- package/lib/logger.d.ts +11 -0
- package/lib/logger.d.ts.map +1 -0
- package/lib/logger.js +23 -14
- package/lib/logger.js.map +1 -0
- package/lib/md5.d.ts +2 -0
- package/lib/md5.d.ts.map +1 -0
- package/lib/md5.js +9 -6
- package/lib/md5.js.map +1 -0
- package/lib/merge.d.ts +2 -0
- package/lib/merge.d.ts.map +1 -0
- package/lib/merge.js +7 -8
- package/lib/merge.js.map +1 -0
- package/lib/proxy.d.ts +6 -0
- package/lib/proxy.d.ts.map +1 -0
- package/lib/proxy.js +65 -0
- package/lib/proxy.js.map +1 -0
- package/lib/retry.d.ts +6 -0
- package/lib/retry.d.ts.map +1 -0
- package/lib/retry.js +11 -13
- package/lib/retry.js.map +1 -0
- package/lib/server.d.ts +14 -0
- package/lib/server.d.ts.map +1 -0
- package/lib/server.js +71 -0
- package/lib/server.js.map +1 -0
- package/lib/socks5.d.ts +8 -0
- package/lib/socks5.d.ts.map +1 -0
- package/lib/socks5.js +24 -27
- package/lib/socks5.js.map +1 -0
- package/lib/timeout.d.ts +2 -0
- package/lib/timeout.d.ts.map +1 -0
- package/lib/timeout.js +7 -0
- package/lib/timeout.js.map +1 -0
- package/lib/transport/creater.d.ts +14 -0
- package/lib/transport/creater.d.ts.map +1 -0
- package/lib/transport/creater.js +72 -0
- package/lib/transport/creater.js.map +1 -0
- package/lib/transport/helper.d.ts +14 -0
- package/lib/transport/helper.d.ts.map +1 -0
- package/lib/transport/helper.js +63 -0
- package/lib/transport/helper.js.map +1 -0
- package/lib/transport/index.d.ts +6 -0
- package/lib/transport/index.d.ts.map +1 -0
- package/lib/transport/index.js +43 -0
- package/lib/transport/index.js.map +1 -0
- package/lib/transport/transport.d.ts +23 -0
- package/lib/transport/transport.d.ts.map +1 -0
- package/lib/transport/transport.js +41 -0
- package/lib/transport/transport.js.map +1 -0
- package/lib/uuid.d.ts +2 -0
- package/lib/uuid.d.ts.map +1 -0
- package/lib/uuid.js +14 -20
- package/lib/uuid.js.map +1 -0
- package/package.json +18 -4
- package/src/deferred.ts +14 -0
- package/src/encrypt.ts +119 -0
- package/src/index.ts +29 -0
- package/src/logger.ts +59 -0
- package/src/md5.ts +5 -0
- package/src/merge.ts +16 -0
- package/src/proxy.ts +65 -0
- package/src/retry.ts +32 -0
- package/src/server.ts +77 -0
- package/src/socks5.ts +46 -0
- package/src/timeout.ts +3 -0
- package/src/transport/creater.ts +88 -0
- package/src/transport/helper.ts +73 -0
- package/src/transport/index.ts +6 -0
- package/src/transport/transport.ts +54 -0
- package/src/uuid.ts +37 -0
- package/.github/workflows/test.yml +0 -30
- package/.prettierrc +0 -11
- package/test/deferred.js +0 -24
- package/test/encrypt.js +0 -25
- package/test/md5.js +0 -9
- package/test/merge.js +0 -11
- package/test/retry.js +0 -61
- package/test/socks5.js +0 -42
- package/test/uuid.js +0 -11
package/src/encrypt.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
|
|
3
|
+
type CipherKey = string;
|
|
4
|
+
interface KeyIVCache {
|
|
5
|
+
[key: string]: [Buffer, Buffer];
|
|
6
|
+
}
|
|
7
|
+
interface KeyIV {
|
|
8
|
+
key: Buffer;
|
|
9
|
+
iv: Buffer;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const bytes_to_key_results: KeyIVCache = {};
|
|
13
|
+
|
|
14
|
+
const EVP_BytesToKey = function (
|
|
15
|
+
password: Buffer,
|
|
16
|
+
key_len: number,
|
|
17
|
+
iv_len: number
|
|
18
|
+
): [Buffer, Buffer] {
|
|
19
|
+
const cacheKey = `${password}:${key_len}:${iv_len}`;
|
|
20
|
+
if (bytes_to_key_results[cacheKey]) {
|
|
21
|
+
return bytes_to_key_results[cacheKey];
|
|
22
|
+
}
|
|
23
|
+
const m: Buffer[] = [];
|
|
24
|
+
let i = 0;
|
|
25
|
+
let count = 0;
|
|
26
|
+
while (count < key_len + iv_len) {
|
|
27
|
+
const md5hash = crypto.createHash('md5');
|
|
28
|
+
let data: Buffer = password;
|
|
29
|
+
if (i > 0) {
|
|
30
|
+
data = Buffer.concat([m[i - 1], password]);
|
|
31
|
+
}
|
|
32
|
+
md5hash.update(data);
|
|
33
|
+
const d = md5hash.digest();
|
|
34
|
+
m.push(d);
|
|
35
|
+
count += d.length;
|
|
36
|
+
i += 1;
|
|
37
|
+
}
|
|
38
|
+
const ms = Buffer.concat(m);
|
|
39
|
+
const key = ms.slice(0, key_len);
|
|
40
|
+
const iv = ms.slice(key_len, key_len + iv_len);
|
|
41
|
+
bytes_to_key_results[cacheKey] = [key, iv];
|
|
42
|
+
return [key, iv];
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type CipherMethod =
|
|
46
|
+
| 'aes-128-cfb'
|
|
47
|
+
| 'aes-192-cfb'
|
|
48
|
+
| 'aes-256-cfb'
|
|
49
|
+
| 'bf-cfb'
|
|
50
|
+
| 'camellia-128-cfb'
|
|
51
|
+
| 'camellia-192-cfb'
|
|
52
|
+
| 'camellia-256-cfb'
|
|
53
|
+
| 'cast5-cfb'
|
|
54
|
+
| 'des-cfb'
|
|
55
|
+
| 'idea-cfb'
|
|
56
|
+
| 'rc2-cfb'
|
|
57
|
+
| 'rc4'
|
|
58
|
+
| 'seed-cfb';
|
|
59
|
+
|
|
60
|
+
const method_supported: Record<CipherMethod, [number, number]> = {
|
|
61
|
+
'aes-128-cfb': [16, 16],
|
|
62
|
+
'aes-192-cfb': [24, 16],
|
|
63
|
+
'aes-256-cfb': [32, 16],
|
|
64
|
+
'bf-cfb': [16, 8],
|
|
65
|
+
'camellia-128-cfb': [16, 16],
|
|
66
|
+
'camellia-192-cfb': [24, 16],
|
|
67
|
+
'camellia-256-cfb': [32, 16],
|
|
68
|
+
'cast5-cfb': [16, 8],
|
|
69
|
+
'des-cfb': [8, 8],
|
|
70
|
+
'idea-cfb': [16, 8],
|
|
71
|
+
'rc2-cfb': [16, 8],
|
|
72
|
+
rc4: [16, 0],
|
|
73
|
+
'seed-cfb': [16, 16],
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export class Encryptor {
|
|
77
|
+
key: CipherKey;
|
|
78
|
+
method: CipherMethod;
|
|
79
|
+
_EVP_KEY: Buffer;
|
|
80
|
+
_IV: Buffer;
|
|
81
|
+
|
|
82
|
+
constructor(key: string = 'MVP123', method: CipherMethod = 'aes-256-cfb') {
|
|
83
|
+
this.key = key;
|
|
84
|
+
this.method = method;
|
|
85
|
+
if (!method_supported[this.method]) {
|
|
86
|
+
throw new Error('Not support method!');
|
|
87
|
+
}
|
|
88
|
+
const keyIV = this.getKeyIV(key, method);
|
|
89
|
+
this._EVP_KEY = keyIV.key;
|
|
90
|
+
this._IV = keyIV.iv;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get_cipher_len(method: string): [number, number] {
|
|
94
|
+
const m = method.toLowerCase() as CipherMethod;
|
|
95
|
+
return method_supported[m];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
getKeyIV(password: string, method: string): KeyIV {
|
|
99
|
+
const m = method.toLowerCase() as CipherMethod;
|
|
100
|
+
const passwordBuf = Buffer.from(password, 'ascii');
|
|
101
|
+
const [keyLen, ivLen] = this.get_cipher_len(m);
|
|
102
|
+
const ref = EVP_BytesToKey(passwordBuf, keyLen, ivLen);
|
|
103
|
+
return { key: ref[0], iv: ref[1] };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
encrypt(buf: Buffer): Buffer {
|
|
107
|
+
const cipher = crypto.createCipheriv(this.method, this._EVP_KEY, this._IV);
|
|
108
|
+
const encrypted = cipher.update(buf);
|
|
109
|
+
const result = cipher.final();
|
|
110
|
+
return Buffer.concat([encrypted, result]);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
decrypt(buf: Buffer): Buffer {
|
|
114
|
+
const decipher = crypto.createDecipheriv(this.method, this._EVP_KEY, this._IV);
|
|
115
|
+
const decrypted = decipher.update(buf);
|
|
116
|
+
const result = decipher.final();
|
|
117
|
+
return Buffer.concat([decrypted, result]);
|
|
118
|
+
}
|
|
119
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export { default as deferred } from './deferred';
|
|
2
|
+
export type { Deferred } from './deferred';
|
|
3
|
+
|
|
4
|
+
export * as encrypt from './encrypt';
|
|
5
|
+
export type { CipherMethod } from './encrypt';
|
|
6
|
+
|
|
7
|
+
export { default as logger } from './logger';
|
|
8
|
+
export type { LogLevel, Logger } from './logger';
|
|
9
|
+
|
|
10
|
+
export { default as md5 } from './md5';
|
|
11
|
+
|
|
12
|
+
export { default as merge } from './merge';
|
|
13
|
+
|
|
14
|
+
export * as proxy from './proxy';
|
|
15
|
+
export type { OnConnect, ConnectCallback } from './proxy';
|
|
16
|
+
|
|
17
|
+
export { default as retry } from './retry';
|
|
18
|
+
export type { RetryOptions } from './retry';
|
|
19
|
+
|
|
20
|
+
export * as server from './server';
|
|
21
|
+
|
|
22
|
+
export * as socks5 from './socks5';
|
|
23
|
+
export type { Socks5AddrInfo } from './socks5';
|
|
24
|
+
|
|
25
|
+
export * as timeout from './timeout';
|
|
26
|
+
|
|
27
|
+
export * as transport from './transport';
|
|
28
|
+
|
|
29
|
+
export { default as uuid } from './uuid';
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type LogLevel = 'fatal' | 'error' | 'warn' | 'info' | 'log' | 'debug';
|
|
2
|
+
|
|
3
|
+
export interface Logger {
|
|
4
|
+
fatal: (msg: string) => void;
|
|
5
|
+
error: (msg: string) => void;
|
|
6
|
+
warn: (msg: string) => void;
|
|
7
|
+
info: (msg: string) => void;
|
|
8
|
+
log: (msg: string) => void;
|
|
9
|
+
debug: (msg: string) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface LevelConfig {
|
|
13
|
+
piv: number;
|
|
14
|
+
colour: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const levelVal: Record<LogLevel, LevelConfig> = {
|
|
18
|
+
fatal: { piv: 1, colour: '\x1b[31m%s\x1b[0m' },
|
|
19
|
+
error: { piv: 2, colour: '\x1b[31m%s\x1b[0m' },
|
|
20
|
+
warn: { piv: 3, colour: '\x1b[33m%s\x1b[0m' },
|
|
21
|
+
info: { piv: 4, colour: '\x1b[32m%s\x1b[0m' },
|
|
22
|
+
log: { piv: 5, colour: '\x1b[37m%s\x1b[0m' },
|
|
23
|
+
debug: { piv: 6, colour: '\x1b[35m%s\x1b[0m' },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function formatDate(date: Date): string {
|
|
27
|
+
const pad = (n: number) => String(n).padStart(2, '0');
|
|
28
|
+
return `${date.getFullYear()}/${pad(date.getMonth() + 1)}/${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default function getCustomLogger(
|
|
32
|
+
label: string,
|
|
33
|
+
level: LogLevel,
|
|
34
|
+
_logFile?: string
|
|
35
|
+
): Logger {
|
|
36
|
+
const noop = () => undefined;
|
|
37
|
+
const setPiv = levelVal[level].piv;
|
|
38
|
+
|
|
39
|
+
function makeLogFn(key: LogLevel): (msg: string) => void {
|
|
40
|
+
if (levelVal[key].piv <= setPiv) {
|
|
41
|
+
const colour = levelVal[key].colour;
|
|
42
|
+
return (msg: string) => {
|
|
43
|
+
const date = formatDate(new Date());
|
|
44
|
+
const prefix = `${date} [${key[0].toUpperCase()}] ${label} `;
|
|
45
|
+
console.log(colour, prefix, msg);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return noop;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
fatal: makeLogFn('fatal'),
|
|
53
|
+
error: makeLogFn('error'),
|
|
54
|
+
warn: makeLogFn('warn'),
|
|
55
|
+
info: makeLogFn('info'),
|
|
56
|
+
log: makeLogFn('log'),
|
|
57
|
+
debug: makeLogFn('debug'),
|
|
58
|
+
};
|
|
59
|
+
}
|
package/src/md5.ts
ADDED
package/src/merge.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default function merge<T extends Record<string, any>>(
|
|
2
|
+
target: T,
|
|
3
|
+
...sources: (Record<string, any> | null | undefined)[]
|
|
4
|
+
): T & Record<string, any> {
|
|
5
|
+
target = target || ({} as T);
|
|
6
|
+
sources.forEach((temp) => {
|
|
7
|
+
if (temp) {
|
|
8
|
+
Object.keys(temp).forEach((key) => {
|
|
9
|
+
if (temp[key] !== null && temp[key] !== undefined) {
|
|
10
|
+
(target as Record<string, any>)[key] = temp[key];
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
return target;
|
|
16
|
+
}
|
package/src/proxy.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import net from 'net';
|
|
2
|
+
import { buildSocks5Addr } from './socks5';
|
|
3
|
+
|
|
4
|
+
export type ConnectCallback = (err?: Error | null) => void;
|
|
5
|
+
export type OnConnect = (addr: Buffer, callback: ConnectCallback) => void;
|
|
6
|
+
|
|
7
|
+
export function createSocks5Proxy(cSock: net.Socket, onConnect: OnConnect): void {
|
|
8
|
+
let stage = 'INIT';
|
|
9
|
+
cSock.on('data', function dataListener(data: Buffer) {
|
|
10
|
+
if (stage === 'INIT') {
|
|
11
|
+
cSock.write('\x05\x00');
|
|
12
|
+
stage = 'ADDR';
|
|
13
|
+
return;
|
|
14
|
+
} else if (stage === 'ADDR') {
|
|
15
|
+
onConnect(data.slice(3), (err) => {
|
|
16
|
+
if (!err) {
|
|
17
|
+
stage = 'STREAM';
|
|
18
|
+
cSock.write('\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00');
|
|
19
|
+
cSock.removeAllListeners('data');
|
|
20
|
+
cSock.pause();
|
|
21
|
+
} else {
|
|
22
|
+
cSock.destroy();
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
cSock.on('error', () => {
|
|
30
|
+
cSock.destroy();
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createConnectProxy(cSock: net.Socket, onConnect: OnConnect): void {
|
|
35
|
+
let stage = 'INIT';
|
|
36
|
+
cSock.on('data', (data: Buffer) => {
|
|
37
|
+
if (stage === 'INIT') {
|
|
38
|
+
const str = data.toString('ascii');
|
|
39
|
+
const lines = str.split(/\s/g);
|
|
40
|
+
if (lines[0] !== 'CONNECT') {
|
|
41
|
+
cSock.destroy();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const hoststr = lines[1];
|
|
45
|
+
const reqhost = hoststr.split(':');
|
|
46
|
+
const hostname = reqhost[0];
|
|
47
|
+
const port = Number(reqhost[1]);
|
|
48
|
+
const socksAddrInfo = buildSocks5Addr(hostname, port);
|
|
49
|
+
onConnect(socksAddrInfo, (err) => {
|
|
50
|
+
if (!err) {
|
|
51
|
+
cSock.write(Buffer.from('HTTP/1.1 200 Connection Established\r\n\r\n'));
|
|
52
|
+
stage = 'STREAM';
|
|
53
|
+
cSock.removeAllListeners('data');
|
|
54
|
+
cSock.pause();
|
|
55
|
+
} else {
|
|
56
|
+
cSock.destroy();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
cSock.on('error', () => {
|
|
63
|
+
cSock.destroy();
|
|
64
|
+
});
|
|
65
|
+
}
|
package/src/retry.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const delay = (time: number): Promise<void> =>
|
|
2
|
+
new Promise((resolve) => setTimeout(resolve, time));
|
|
3
|
+
|
|
4
|
+
export interface RetryOptions {
|
|
5
|
+
times?: number;
|
|
6
|
+
interval?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function retryPromise<T>(
|
|
10
|
+
fn: () => Promise<T>,
|
|
11
|
+
opts: RetryOptions = {},
|
|
12
|
+
errcall?: (err: Error) => void
|
|
13
|
+
): Promise<T> {
|
|
14
|
+
const retryCount = opts.times !== undefined ? opts.times : 5;
|
|
15
|
+
|
|
16
|
+
function invoker(idx: number): Promise<T> {
|
|
17
|
+
if (idx >= retryCount) {
|
|
18
|
+
return Promise.reject(
|
|
19
|
+
new Error(`expect retryCount ${retryCount} is attached!`)
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return fn().catch((err: Error) => {
|
|
23
|
+
errcall && errcall(err);
|
|
24
|
+
if (opts.interval) {
|
|
25
|
+
return delay(opts.interval).then(() => invoker(idx + 1));
|
|
26
|
+
} else {
|
|
27
|
+
return invoker(idx + 1);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return invoker(0);
|
|
32
|
+
}
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import net from 'net';
|
|
2
|
+
import http from 'http';
|
|
3
|
+
import tls from 'tls';
|
|
4
|
+
import http2 from 'http2';
|
|
5
|
+
import url from 'url';
|
|
6
|
+
import WebSocket from 'ws';
|
|
7
|
+
|
|
8
|
+
export type HttpHandler = (req: http.IncomingMessage, res: http.ServerResponse) => void;
|
|
9
|
+
export type WsHandler = (ws: WebSocket) => void;
|
|
10
|
+
export type StreamHandler = (stream: net.Socket | http2.ServerHttp2Stream) => void;
|
|
11
|
+
export type ConnHandler = (conn: net.Socket | tls.TLSSocket) => void;
|
|
12
|
+
|
|
13
|
+
export function createWsServer(
|
|
14
|
+
workPath: string,
|
|
15
|
+
handler: WsHandler,
|
|
16
|
+
httpHandler?: HttpHandler
|
|
17
|
+
): http.Server {
|
|
18
|
+
const server = http.createServer(function (req, res) {
|
|
19
|
+
if (httpHandler) {
|
|
20
|
+
httpHandler(req, res);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
// ws 2.x exposes Server as WebSocket.Server
|
|
24
|
+
const WsServer = (WebSocket as any).Server as typeof WebSocket;
|
|
25
|
+
const wss: any = new (WsServer as any)({ noServer: true, clientTracking: false });
|
|
26
|
+
wss.on('connection', function (wsconn: WebSocket) {
|
|
27
|
+
handler(wsconn);
|
|
28
|
+
});
|
|
29
|
+
server.on('upgrade', function (request, socket, head) {
|
|
30
|
+
const pathname = url.parse(request.url || '').pathname;
|
|
31
|
+
if (pathname === workPath) {
|
|
32
|
+
wss.handleUpgrade(request, socket as net.Socket, head, function done(ws: WebSocket) {
|
|
33
|
+
wss.emit('connection', ws, request);
|
|
34
|
+
});
|
|
35
|
+
} else {
|
|
36
|
+
socket.destroy();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return server;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createHttp2Server(
|
|
43
|
+
tlsOpts: tls.TlsOptions,
|
|
44
|
+
workPath: string,
|
|
45
|
+
handler: (stream: http2.ServerHttp2Stream) => void,
|
|
46
|
+
httpHandler?: (req: http2.Http2ServerRequest, res: http2.Http2ServerResponse) => void
|
|
47
|
+
): http2.Http2SecureServer {
|
|
48
|
+
const http2Opts = Object.assign({ allowHTTP1: true }, tlsOpts);
|
|
49
|
+
const server = http2.createSecureServer(http2Opts, function (req, res) {
|
|
50
|
+
if (httpHandler) {
|
|
51
|
+
httpHandler(req, res);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
server.on('stream', function (stream: http2.ServerHttp2Stream, headers: http2.IncomingHttpHeaders) {
|
|
55
|
+
const path = headers[':path'];
|
|
56
|
+
if (path === workPath) {
|
|
57
|
+
handler(stream);
|
|
58
|
+
} else {
|
|
59
|
+
stream.destroy();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return server;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createTcpServer(handler: ConnHandler): net.Server {
|
|
66
|
+
const server = net.createServer(function (conn) {
|
|
67
|
+
handler(conn);
|
|
68
|
+
});
|
|
69
|
+
return server;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function createTlsServer(tlsOpts: tls.TlsOptions, handler: ConnHandler): tls.Server {
|
|
73
|
+
const server = tls.createServer(tlsOpts, function (conn) {
|
|
74
|
+
handler(conn);
|
|
75
|
+
});
|
|
76
|
+
return server;
|
|
77
|
+
}
|
package/src/socks5.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface Socks5AddrInfo {
|
|
2
|
+
dstAddr: string;
|
|
3
|
+
dstPort: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function parseSocks5Addr(buf: Buffer, offset = 0): Socks5AddrInfo {
|
|
7
|
+
if (offset > 0) {
|
|
8
|
+
buf = buf.slice(offset);
|
|
9
|
+
}
|
|
10
|
+
const type = buf.readUInt8(0);
|
|
11
|
+
const addrData = buf.slice(1);
|
|
12
|
+
let dstAddr = '';
|
|
13
|
+
let dstPort = 0;
|
|
14
|
+
|
|
15
|
+
if (type === 0x01) {
|
|
16
|
+
const IP = addrData.slice(0, -2);
|
|
17
|
+
const PORT = addrData.slice(-2);
|
|
18
|
+
dstAddr = Array.from(IP)
|
|
19
|
+
.map((temp) => Number(temp).toString(10))
|
|
20
|
+
.join('.');
|
|
21
|
+
dstPort = PORT[0] * 256 + PORT[1];
|
|
22
|
+
} else if (type === 0x03) {
|
|
23
|
+
const addrLen = addrData.readUInt8(0);
|
|
24
|
+
const domain = addrData.slice(1, addrLen + 1);
|
|
25
|
+
const port = addrData.slice(addrLen + 1);
|
|
26
|
+
dstAddr = domain.toString();
|
|
27
|
+
dstPort = port[0] * 256 + port[1];
|
|
28
|
+
}
|
|
29
|
+
return { dstAddr, dstPort };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function buildSocks5Addr(hostname: string, port: number): Buffer {
|
|
33
|
+
if (/^(\d+\.){3}\d+$/.test(hostname)) {
|
|
34
|
+
const parts = hostname.split('.').map((p) => Number(p));
|
|
35
|
+
const portBuf = Buffer.from([Math.floor(port / 256), port % 256]);
|
|
36
|
+
const preBuf = Buffer.from([0x01, parts[0], parts[1], parts[2], parts[3]]);
|
|
37
|
+
return Buffer.concat([preBuf, portBuf]);
|
|
38
|
+
} else {
|
|
39
|
+
const preBuf = Buffer.from([0x03, hostname.length]);
|
|
40
|
+
const domainBuf = Buffer.from(hostname);
|
|
41
|
+
const portBuf = Buffer.from([Math.floor(port / 256), port % 256]);
|
|
42
|
+
return Buffer.concat([preBuf, domainBuf, portBuf]);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const parseAddrInfo = parseSocks5Addr;
|
package/src/timeout.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import WebSocket from 'ws';
|
|
2
|
+
import net from 'net';
|
|
3
|
+
import http2 from 'http2';
|
|
4
|
+
import tls from 'tls';
|
|
5
|
+
import { Transport, TransportConn } from './transport';
|
|
6
|
+
|
|
7
|
+
export interface TransportParams {
|
|
8
|
+
host: string;
|
|
9
|
+
port: number;
|
|
10
|
+
path?: string;
|
|
11
|
+
secure?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createWebsocketTransport(
|
|
15
|
+
params: TransportParams,
|
|
16
|
+
onOpen: () => void
|
|
17
|
+
): Transport {
|
|
18
|
+
const tunnelWsUrl = `${params.secure ? 'wss' : 'ws'}://${params.host}:${params.port}${params.path || ''}`;
|
|
19
|
+
const ws = new WebSocket(tunnelWsUrl, {
|
|
20
|
+
perMessageDeflate: false,
|
|
21
|
+
handshakeTimeout: 3000,
|
|
22
|
+
} as any);
|
|
23
|
+
const ts = new Transport({ type: 'ws', conn: ws as unknown as TransportConn });
|
|
24
|
+
ws.on('open', onOpen);
|
|
25
|
+
return ts;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createHttp2Transport(
|
|
29
|
+
params: TransportParams,
|
|
30
|
+
onOpen: () => void
|
|
31
|
+
): Transport {
|
|
32
|
+
const http2Url = `https://${params.host}:${params.port}`;
|
|
33
|
+
const client = http2.connect(http2Url, {
|
|
34
|
+
rejectUnauthorized: false,
|
|
35
|
+
requestCert: true,
|
|
36
|
+
});
|
|
37
|
+
const http2stream = client.request({
|
|
38
|
+
':method': 'POST',
|
|
39
|
+
':path': params.path || '/',
|
|
40
|
+
'Content-Type': 'octet-stream',
|
|
41
|
+
});
|
|
42
|
+
http2stream.on('response', onOpen);
|
|
43
|
+
const ts = new Transport({ type: 'h2', conn: http2stream as unknown as TransportConn });
|
|
44
|
+
return ts;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createTlsTransport(
|
|
48
|
+
params: TransportParams,
|
|
49
|
+
onOpen: () => void
|
|
50
|
+
): Transport {
|
|
51
|
+
const tlsOpts: tls.ConnectionOptions = {
|
|
52
|
+
rejectUnauthorized: false,
|
|
53
|
+
host: params.host,
|
|
54
|
+
port: params.port,
|
|
55
|
+
};
|
|
56
|
+
const tlsConn = tls.connect(tlsOpts, function () {
|
|
57
|
+
onOpen();
|
|
58
|
+
});
|
|
59
|
+
const ts = new Transport({ type: 'tls', conn: tlsConn as unknown as TransportConn });
|
|
60
|
+
return ts;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function createTcpTransport(
|
|
64
|
+
params: TransportParams,
|
|
65
|
+
onOpen: () => void
|
|
66
|
+
): Transport {
|
|
67
|
+
const socket = new net.Socket();
|
|
68
|
+
socket.connect(params.port, params.host, function () {
|
|
69
|
+
onOpen();
|
|
70
|
+
});
|
|
71
|
+
return new Transport({ type: 'tcp', conn: socket as unknown as TransportConn });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function createUnixsocketTransport(
|
|
75
|
+
params: TransportParams,
|
|
76
|
+
onOpen: () => void
|
|
77
|
+
): Transport {
|
|
78
|
+
const socket = new net.Socket();
|
|
79
|
+
socket.connect(params.path || '', function () {
|
|
80
|
+
onOpen();
|
|
81
|
+
});
|
|
82
|
+
const ts = new Transport({ type: 'domainsocket', conn: socket as unknown as TransportConn });
|
|
83
|
+
return ts;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function wrapSocket(type: string, conn: TransportConn): Transport {
|
|
87
|
+
return new Transport({ type: type as any, conn });
|
|
88
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import WebSocket from 'ws';
|
|
2
|
+
import net from 'net';
|
|
3
|
+
import stream from 'stream';
|
|
4
|
+
|
|
5
|
+
export type DataCallback = (data: Buffer) => void;
|
|
6
|
+
export type ErrorCallback = (err: Error) => void;
|
|
7
|
+
export type CloseCallback = (code?: number) => void;
|
|
8
|
+
|
|
9
|
+
export function bindStreamSocket(
|
|
10
|
+
s: stream.Readable & { on: Function; destroy: Function },
|
|
11
|
+
onData: DataCallback,
|
|
12
|
+
onError: ErrorCallback,
|
|
13
|
+
onClose: CloseCallback
|
|
14
|
+
): void {
|
|
15
|
+
let buffcache = Buffer.from([]);
|
|
16
|
+
s.on('data', function (data: Buffer) {
|
|
17
|
+
buffcache = Buffer.concat([buffcache, data]);
|
|
18
|
+
while (true) {
|
|
19
|
+
if (buffcache.length <= 2) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const datalen = buffcache[0] * 256 + buffcache[1];
|
|
23
|
+
if (buffcache.length < datalen + 2) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const pack = buffcache.slice(2, datalen + 2);
|
|
27
|
+
buffcache = buffcache.slice(datalen + 2);
|
|
28
|
+
onData(pack);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
s.on('close', function (code?: number) {
|
|
32
|
+
onClose(code);
|
|
33
|
+
});
|
|
34
|
+
s.on('error', function (err: Error) {
|
|
35
|
+
(s as any).destroy();
|
|
36
|
+
onError(err);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function bindWebsocket(
|
|
41
|
+
ws: WebSocket,
|
|
42
|
+
onData: DataCallback,
|
|
43
|
+
onError: ErrorCallback,
|
|
44
|
+
onClose: CloseCallback
|
|
45
|
+
): void {
|
|
46
|
+
ws.on('message', onData);
|
|
47
|
+
ws.on('close', (code: number) => {
|
|
48
|
+
onClose(code);
|
|
49
|
+
});
|
|
50
|
+
ws.on('error', (err: Error) => {
|
|
51
|
+
ws.close();
|
|
52
|
+
onError(err);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function tcpsocketSend(socket: net.Socket, data: Buffer): void {
|
|
57
|
+
const datalen = data.length;
|
|
58
|
+
if (socket.writable) {
|
|
59
|
+
socket.write(
|
|
60
|
+
Buffer.concat([Buffer.from([datalen >> 8, datalen % 256]), data])
|
|
61
|
+
);
|
|
62
|
+
} else {
|
|
63
|
+
throw new Error('socket cannot writeable!');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function websocketSend(ws: WebSocket, data: Buffer): void {
|
|
68
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
69
|
+
ws.send(data, { binary: true });
|
|
70
|
+
} else {
|
|
71
|
+
throw new Error('ws socket not open!' + ws.readyState);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import WebSocket from 'ws';
|
|
2
|
+
import net from 'net';
|
|
3
|
+
import { websocketSend, tcpsocketSend, bindWebsocket, bindStreamSocket } from './helper';
|
|
4
|
+
import type { DataCallback, ErrorCallback, CloseCallback } from './helper';
|
|
5
|
+
|
|
6
|
+
export type TransportType = 'ws' | 'h2' | 'tcp' | 'tls' | 'domainsocket';
|
|
7
|
+
|
|
8
|
+
export interface TransportConn {
|
|
9
|
+
close?: () => void;
|
|
10
|
+
destroy?: () => void;
|
|
11
|
+
writable?: boolean;
|
|
12
|
+
readyState?: number;
|
|
13
|
+
send?: Function;
|
|
14
|
+
on: (event: string, listener: Function) => any;
|
|
15
|
+
write?: (data: Buffer) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class Transport {
|
|
19
|
+
type: TransportType;
|
|
20
|
+
conn: TransportConn;
|
|
21
|
+
|
|
22
|
+
constructor(opts: { type: TransportType; conn: TransportConn }) {
|
|
23
|
+
this.type = opts.type;
|
|
24
|
+
this.conn = opts.conn;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
sendPacket(binarydata: Buffer): void {
|
|
28
|
+
if (this.type === 'ws') {
|
|
29
|
+
websocketSend(this.conn as unknown as WebSocket, binarydata);
|
|
30
|
+
} else {
|
|
31
|
+
tcpsocketSend(this.conn as unknown as net.Socket, binarydata);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
bindEvents(onData: DataCallback, onError: ErrorCallback, onClose: CloseCallback): void {
|
|
36
|
+
if (this.type === 'ws') {
|
|
37
|
+
bindWebsocket(this.conn as unknown as WebSocket, onData, onError, onClose);
|
|
38
|
+
} else {
|
|
39
|
+
bindStreamSocket(this.conn as any, onData, onError, onClose);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
close(): void {
|
|
44
|
+
try {
|
|
45
|
+
if (this.type === 'ws' || this.type === 'h2') {
|
|
46
|
+
(this.conn as any).close();
|
|
47
|
+
} else {
|
|
48
|
+
(this.conn as any).destroy();
|
|
49
|
+
}
|
|
50
|
+
} catch (_err) {
|
|
51
|
+
// ignore
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|