js-tcp-tunnel 1.0.3 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-tcp-tunnel",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "js tcp tunnel",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -24,10 +24,10 @@
24
24
  },
25
25
  "homepage": "https://github.com/yuanliwei/js-tunnel#readme",
26
26
  "devDependencies": {
27
- "@types/node": "^22.9.4",
28
- "koa": "^2.15.3",
29
- "koa-router": "^13.0.1",
27
+ "@koa/router": "^14.0.0",
28
+ "@types/node": "^24.2.1",
29
+ "koa": "^3.0.1",
30
30
  "log4js": "^6.9.1",
31
- "ws": "^8.18.0"
31
+ "ws": "^8.18.3"
32
32
  }
33
33
  }
package/src/lib.js CHANGED
@@ -7,7 +7,7 @@ import { ReadableStream, TransformStream, WritableStream } from 'node:stream/web
7
7
 
8
8
  /**
9
9
  * @import {WebSocketServer} from 'ws'
10
- * @import Router from 'koa-router'
10
+ * @import Router from '@koa/router'
11
11
  * @import {SocketChannel, TCP_TUNNEL_DATA, TUNNEL_TCP_CLIENT_HELPER, TUNNEL_TCP_DATA_CONNECT, TUNNEL_TCP_DATA_LISTEN, TUNNEL_TCP_DATA_PINGPONG, TUNNEL_TCP_SERVER_HELPER, TunnelTcpClientHelperParam, TunnelTcpServerHelperParam} from './types.js'
12
12
  */
13
13
 
@@ -68,8 +68,8 @@ export function Promise_withResolvers() {
68
68
  }
69
69
 
70
70
  /**
71
- * @param {Promise<[CryptoKey,Uint8Array]>} key_iv
72
- * @returns {TransformStream<Uint8Array, Uint8Array>}
71
+ * @param {Promise<[CryptoKey,Uint8Array<ArrayBuffer>]>} key_iv
72
+ * @returns {TransformStream<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>}
73
73
  */
74
74
  export function createEncodeStream(key_iv) {
75
75
  let key = null
@@ -86,8 +86,8 @@ export function createEncodeStream(key_iv) {
86
86
  }
87
87
 
88
88
  /**
89
- * @param {Promise<[CryptoKey,Uint8Array]>} key_iv
90
- * @returns {TransformStream<Uint8Array, Uint8Array>}
89
+ * @param {Promise<[CryptoKey,Uint8Array<ArrayBuffer>]>} key_iv
90
+ * @returns {TransformStream<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>}
91
91
  */
92
92
  export function createDecodeStream(key_iv) {
93
93
  let key = null
@@ -110,10 +110,10 @@ export function createDecodeStream(key_iv) {
110
110
  const HEADER_CHECK = 0xb1f7705f
111
111
 
112
112
  /**
113
- * @param {Uint8Array[]} queue
113
+ * @param {Uint8Array<ArrayBuffer>[]} queue
114
114
  * @param {CryptoKey} key
115
- * @param {Uint8Array} iv
116
- * @returns {Promise<Uint8Array>}
115
+ * @param {Uint8Array<ArrayBuffer>} iv
116
+ * @returns {Promise<Uint8Array<ArrayBuffer>>}
117
117
  */
118
118
  export async function buildBufferData(queue, key, iv) {
119
119
  let buffers = []
@@ -132,11 +132,11 @@ export async function buildBufferData(queue, key, iv) {
132
132
  /**
133
133
  * @param {Uint8Array<ArrayBuffer>} buffer
134
134
  * @param {CryptoKey} key
135
- * @param {Uint8Array} iv
136
- * @returns {Promise<[Uint8Array[],Uint8Array<ArrayBuffer>]>}
135
+ * @param {Uint8Array<ArrayBuffer>} iv
136
+ * @returns {Promise<[Uint8Array<ArrayBuffer>[],Uint8Array<ArrayBuffer>]>}
137
137
  */
138
138
  export async function parseBufferData(buffer, key, iv) {
139
- /** @type{Uint8Array[]} */
139
+ /** @type{Uint8Array<ArrayBuffer>[]} */
140
140
  let queue = []
141
141
  let offset = 0
142
142
  let remain = new Uint8Array(0)
@@ -315,7 +315,7 @@ export function printTcpTunnelData(data) {
315
315
  *
316
316
  * @param {string} password
317
317
  * @param {number} iterations
318
- * @returns {Promise<[CryptoKey,Uint8Array]>}
318
+ * @returns {Promise<[CryptoKey,Uint8Array<ArrayBuffer>]>}
319
319
  */
320
320
  export async function buildKeyIv(password, iterations) {
321
321
  if (!TUNNEL_TCP_WITH_CRYPTO) return [null, null]
@@ -351,9 +351,9 @@ export async function buildKeyIv(password, iterations) {
351
351
 
352
352
  /**
353
353
  *
354
- * @param {Uint8Array} data
354
+ * @param {Uint8Array<ArrayBuffer>} data
355
355
  * @param {CryptoKey} key
356
- * @param {Uint8Array} iv
356
+ * @param {Uint8Array<ArrayBuffer>} iv
357
357
  * @returns
358
358
  */
359
359
  export async function encrypt(data, key, iv) {
@@ -366,9 +366,9 @@ export async function encrypt(data, key, iv) {
366
366
  }
367
367
 
368
368
  /**
369
- * @param {Uint8Array} data
369
+ * @param {Uint8Array<ArrayBuffer>} data
370
370
  * @param {CryptoKey} key
371
- * @param {Uint8Array} iv
371
+ * @param {Uint8Array<ArrayBuffer>} iv
372
372
  * @returns
373
373
  */
374
374
  export async function decrypt(data, key, iv) {
@@ -437,7 +437,7 @@ export function buildTcpTunnelData(box) {
437
437
  }
438
438
 
439
439
  /**
440
- * @param {Uint8Array} buffer
440
+ * @param {Uint8Array<ArrayBuffer>} buffer
441
441
  */
442
442
  export function parseTcpTunnelData(buffer) {
443
443
  let offset = 0
@@ -534,25 +534,43 @@ export function pipeSocketDataWithChannel(channelMap, channelId, encodeWriter) {
534
534
  let signal = Promise_withResolvers()
535
535
  signal.resolve()
536
536
  let sendPackSize = 0
537
- let recvPackSize = 0
537
+ let remoteRecvPackSize = 0
538
538
  channel.notify = (size) => {
539
- recvPackSize = size
539
+ remoteRecvPackSize = size
540
540
  signal.resolve()
541
541
  }
542
542
  let [clientKey, clientIv] = channel.key_iv
543
543
  let bufferedTransform = createTimeBufferedTransformStream(50)
544
+ let backPressureTimer = null
544
545
  Readable.toWeb(socket).pipeThrough(bufferedTransform).pipeTo(new WritableStream({
546
+ /**
547
+ * @param {Uint8Array<ArrayBuffer>} chunk
548
+ */
545
549
  async write(chunk) {
546
550
  const buffer = await encrypt(chunk, clientKey, clientIv)
547
- let bufferPackSize = sendPackSize - recvPackSize
551
+ let bufferPackSize = sendPackSize - remoteRecvPackSize
552
+ if (DEBUG_TUNNEL_TCP) {
553
+ console.warn('bufferPackSize:', bufferPackSize)
554
+ }
548
555
  if (bufferPackSize > 10) {
549
556
  signal.resolve()
550
557
  signal = Promise_withResolvers()
558
+ const s = signal
559
+ backPressureTimer = setTimeout(() => {
560
+ s.resolve()
561
+ console.error('pipeSocketDataWithChannel timeout close channel')
562
+ sendPackSize = 0
563
+ this.close()
564
+ }, 10_000).unref()
551
565
  if (DEBUG_TUNNEL_TCP) {
552
- console.info('stop wait signal', ' sendPackSize:', sendPackSize, ' recvPackSize:', recvPackSize, ' bufferPackSize:', bufferPackSize)
566
+ console.info('stop wait signal', ' sendPackSize:', sendPackSize, ' recvPackSize:', remoteRecvPackSize, ' bufferPackSize:', bufferPackSize)
553
567
  }
554
568
  }
555
569
  await signal.promise
570
+ if (backPressureTimer) {
571
+ clearTimeout(backPressureTimer)
572
+ backPressureTimer = null
573
+ }
556
574
  await encodeWriter.write(buildTcpTunnelData({
557
575
  type: TUNNEL_TCP_TYPE_DATA,
558
576
  srcId: channel.srcId,
@@ -573,6 +591,7 @@ export function pipeSocketDataWithChannel(channelMap, channelId, encodeWriter) {
573
591
  buffer: new Uint8Array(0),
574
592
  })).catch((err) => { console.error('web stream write error', err.message) })
575
593
  channelMap.delete(channelId)
594
+ socket.destroy()
576
595
  }
577
596
  })).catch((err) => {
578
597
  console.error('web stream error', err.message)
@@ -611,8 +630,8 @@ function natTunnelData(param, data) {
611
630
 
612
631
  /**
613
632
  * @param {TunnelTcpServerHelperParam} param
614
- * @param {WritableStreamDefaultWriter<Uint8Array<ArrayBufferLike>>} encodeWriter
615
- * @param {Uint8Array<ArrayBufferLike>} chunk
633
+ * @param {WritableStreamDefaultWriter<Uint8Array<ArrayBuffer>>} encodeWriter
634
+ * @param {Uint8Array<ArrayBuffer>} chunk
616
635
  */
617
636
  async function dispatchServerBufferData(param, encodeWriter, chunk) {
618
637
  let data = parseTcpTunnelData(chunk)
@@ -658,10 +677,10 @@ async function dispatchServerBufferData(param, encodeWriter, chunk) {
658
677
  /**
659
678
  * @param {TunnelTcpClientHelperParam} param
660
679
  * @param {{ (): Promise<void>; }} setup
661
- * @param {Map<string,{host:string;port:number;key_iv:[CryptoKey, Uint8Array]}>} listenKeyParamMap
680
+ * @param {Map<string,{host:string;port:number;key_iv:[CryptoKey, Uint8Array<ArrayBuffer>]}>} listenKeyParamMap
662
681
  * @param {Map<number, SocketChannel>} channelMap
663
- * @param {WritableStreamDefaultWriter<Uint8Array<ArrayBufferLike>>} encodeWriter
664
- * @param {Uint8Array<ArrayBufferLike>} buffer
682
+ * @param {WritableStreamDefaultWriter<Uint8Array<ArrayBuffer>>} encodeWriter
683
+ * @param {Uint8Array<ArrayBuffer>} buffer
665
684
  */
666
685
  async function dispatchClientBufferData(param, setup, listenKeyParamMap, channelMap, encodeWriter, buffer) {
667
686
  let data = parseTcpTunnelData(buffer)
@@ -894,7 +913,7 @@ export function createTunnelTcpClientHelper(param) {
894
913
  /** @type{Map<number,SocketChannel>} */
895
914
  let channelMap = new Map()
896
915
 
897
- /** @type{Map<string,{host:string;port:number;key_iv:[CryptoKey, Uint8Array]}>} */
916
+ /** @type{Map<string,{host:string;port:number;key_iv:[CryptoKey, Uint8Array<ArrayBuffer>]}>} */
898
917
  let listenKeyParamMap = new Map()
899
918
 
900
919
  let server_key_iv = buildKeyIv(param.serverKey, 10)
@@ -921,6 +940,9 @@ export function createTunnelTcpClientHelper(param) {
921
940
  }
922
941
 
923
942
  decode.readable.pipeTo(new WritableStream({
943
+ /**
944
+ * @param {Uint8Array<ArrayBuffer>} buffer
945
+ */
924
946
  async write(buffer) {
925
947
  try {
926
948
  await dispatchClientBufferData(param, setup, listenKeyParamMap, channelMap, encodeWriter, buffer)
@@ -1351,6 +1373,9 @@ export function createTunnelTcpClientHttp(param) {
1351
1373
  let socketWriter = null
1352
1374
  let bufferedTransform = createTimeBufferedTransformStream(50)
1353
1375
  helper.readable.pipeThrough(bufferedTransform).pipeTo(new WritableStream({
1376
+ /**
1377
+ * @param {Uint8Array<ArrayBufferLike>} chunk
1378
+ */
1354
1379
  async write(chunk) {
1355
1380
  while (!param.signal.aborted && socketWriter == null) {
1356
1381
  await signal.promise
package/src/lib.test.js CHANGED
@@ -5,7 +5,7 @@ import net from 'net'
5
5
  import http from 'http'
6
6
  import { Readable, Transform } from 'node:stream'
7
7
  import Koa from 'koa'
8
- import Router from 'koa-router'
8
+ import Router from '@koa/router'
9
9
  import { WebSocketServer } from 'ws'
10
10
  import log4js from 'log4js'
11
11
 
@@ -509,4 +509,144 @@ test('test-connect', { skip: SKIP_MANAUAL_TEST }, async () => {
509
509
 
510
510
  await sleep(10000_000)
511
511
  })
512
+ })
513
+
514
+ test('terminal-server', async () => {
515
+ // node --test-name-pattern="^terminal-server$" src/lib.test.js
516
+ using stack = new DisposableStack()
517
+ const ac = new AbortController()
518
+ stack.adopt(ac, () => { ac.abort() })
519
+ let transformSize = 0
520
+
521
+ async function createEchoServer() {
522
+ console.info('创建socket服务')
523
+ let socketServer = net.createServer((socket) => {
524
+ socket.pipe(new Transform({
525
+ transform(chunk, _, callback) {
526
+ // console.info('transform chunk', chunk.length)
527
+ transformSize += chunk.length
528
+ this.push("echo::::")
529
+ this.push(chunk)
530
+ callback()
531
+ }
532
+ })).pipe(socket).on('error', (err) => console.error(err.message))
533
+ }).listen(9036)
534
+ await sleep(100)
535
+ socketServer.on('error', (err) => {
536
+ console.error(err.message)
537
+ })
538
+ ac.signal.addEventListener('abort', () => {
539
+ socketServer.close()
540
+ })
541
+ }
542
+
543
+ /**
544
+ *
545
+ * @param {AbortController} ac
546
+ */
547
+ async function createDispatchServer(ac) {
548
+ console.info('创建转发服务 http')
549
+ let app = new Koa()
550
+ let router = new Router()
551
+ createTunnelTcpServerKoaRouter({
552
+ signal: ac.signal,
553
+ router: router,
554
+ path: '/tunnel/0f7c5b2c9080eaa9e4d6139126daac04',
555
+ serverKey: '2934c57f790f9e99a52a121802df231c',
556
+ })
557
+ app.use(router.routes())
558
+ app.use(router.allowedMethods())
559
+ app.onerror = (err) => console.info(err.message)
560
+ let koaServer = http.createServer(app.callback())
561
+ koaServer.listen(9035)
562
+ koaServer.on('error', (e) => { console.error(e.message) })
563
+ ac.signal.addEventListener('abort', () => {
564
+ console.info('close koa server')
565
+ koaServer.close((err) => {
566
+ console.info('on server close result ', err)
567
+ })
568
+ koaServer.closeAllConnections()
569
+ })
570
+ await sleep(1000)
571
+ }
572
+
573
+ async function createListenClient() {
574
+ console.info('创建监听服务 http')
575
+ let connection1 = createTunnelTcpClientHttp({
576
+ url: 'http://127.0.0.1:9035/tunnel/0f7c5b2c9080eaa9e4d6139126daac04',
577
+ signal: ac.signal,
578
+ serverKey: '2934c57f790f9e99a52a121802df231c',
579
+ })
580
+ connection1.listen({
581
+ clientKey: 'mmm',
582
+ tunnelKey: 'b0f5014acad2060d6bd3730a1721c97f',
583
+ host: '127.0.0.1',
584
+ port: 9036,
585
+ })
586
+ await sleep(100)
587
+ }
588
+
589
+ async function createConnectClient() {
590
+ console.info('创建连接服务 http 1')
591
+ let connection2 = createTunnelTcpClientHttp({
592
+ url: 'http://127.0.0.1:9035/tunnel/0f7c5b2c9080eaa9e4d6139126daac04',
593
+ signal: ac.signal,
594
+ serverKey: '2934c57f790f9e99a52a121802df231c',
595
+ })
596
+ connection2.connect({
597
+ clientKey: 'mmm',
598
+ tunnelKey: 'b0f5014acad2060d6bd3730a1721c97f',
599
+ port: 9037,
600
+ })
601
+
602
+ await sleep(1000)
603
+ }
604
+
605
+ let socketCreateCount = 0
606
+ let recvCount = 0
607
+ async function startSendRecvTest() {
608
+ socketCreateCount++
609
+ console.info('tcp透传测试 start')
610
+ let socket1 = net.createConnection({ host: '127.0.0.1', port: 9037 })
611
+ socket1.on('error', (err) => { console.error(err.message) })
612
+ ac.signal.addEventListener('abort', () => { socket1.destroy() })
613
+ socket1.on('connect', async () => {
614
+ socket1.on('data', (chunk) => {
615
+ console.info('receive chunk ', Uint8Array_toString(chunk))
616
+ recvCount++
617
+ })
618
+ while (!ac.signal.aborted && !socket1.closed) {
619
+ await sleep(1000)
620
+ socket1.write(`iiiiiii>>>>>>> ${new Date().toLocaleString()}`)
621
+ }
622
+ console.info('finished socket.')
623
+ setTimeout(() => {
624
+ startSendRecvTest()
625
+ }, 1)
626
+ })
627
+ }
628
+
629
+ await createEchoServer()
630
+ let ac2 = new AbortController()
631
+ stack.adopt(0, () => ac2.abort())
632
+ await createDispatchServer(ac2)
633
+ await createListenClient()
634
+ await createConnectClient()
635
+ await startSendRecvTest()
636
+
637
+ await sleep(5000)
638
+ ac2.abort()
639
+ console.info('stop dispatch server')
640
+ await sleep(5_000)
641
+ ac2 = new AbortController()
642
+ stack.adopt(0, () => ac2.abort())
643
+ console.info('recreate dispatch server')
644
+ await createDispatchServer(ac2)
645
+
646
+ await sleep(20_000)
647
+
648
+ strictEqual(socketCreateCount, 2)
649
+ strictEqual(recvCount, 14)
650
+
651
+ console.info('over!')
512
652
  })
package/src/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import net from 'node:net'
2
- import { WritableStream } from "stream/web"
2
+ import { WritableStream, ReadableStream, ReadableStreamDefaultReader, WritableStreamDefaultWriter } from "stream/web"
3
3
 
4
4
 
5
5
  export type PromiseResolvers = {
@@ -38,7 +38,7 @@ export type TCP_TUNNEL_DATA = {
38
38
  dstId: number
39
39
  srcChannel: number
40
40
  dstChannel: number
41
- buffer: Uint8Array
41
+ buffer: Uint8Array<ArrayBuffer>
42
42
  }
43
43
 
44
44
  export type TUNNEL_TCP_DATA_LISTEN = {
@@ -59,18 +59,18 @@ export type TUNNEL_TCP_SERVER = {
59
59
  }
60
60
 
61
61
  export type TUNNEL_TCP_SERVER_HELPER = {
62
- readable: ReadableStream<Uint8Array>
63
- writable: WritableStream<Uint8Array>
64
- reader: ReadableStreamDefaultReader<Uint8Array>
65
- writer: WritableStreamDefaultWriter<Uint8Array>
62
+ readable: ReadableStream<Uint8Array<ArrayBuffer>>
63
+ writable: WritableStream<Uint8Array<ArrayBuffer>>
64
+ reader: ReadableStreamDefaultReader<Uint8Array<ArrayBuffer>>
65
+ writer: WritableStreamDefaultWriter<Uint8Array<ArrayBuffer>>
66
66
  dstId: number
67
67
  }
68
68
 
69
69
  export type TUNNEL_TCP_CLIENT_HELPER = {
70
- readable: ReadableStream<Uint8Array>
71
- writable: WritableStream<Uint8Array>
72
- reader: ReadableStreamDefaultReader<Uint8Array>
73
- writer: WritableStreamDefaultWriter<Uint8Array>
70
+ readable: ReadableStream<Uint8Array<ArrayBuffer>>
71
+ writable: WritableStream<Uint8Array<ArrayBuffer>>
72
+ reader: ReadableStreamDefaultReader<Uint8Array<ArrayBuffer>>
73
+ writer: WritableStreamDefaultWriter<Uint8Array<ArrayBuffer>>
74
74
  param: TunnelTcpClientHelperParam
75
75
  listen: (param: {
76
76
  clientKey?: string
@@ -87,7 +87,7 @@ export type TUNNEL_TCP_CLIENT_HELPER = {
87
87
 
88
88
 
89
89
  export type SocketChannel = {
90
- writer: WritableStreamDefaultWriter<Uint8Array>
90
+ writer: WritableStreamDefaultWriter<Uint8Array<ArrayBuffer>>
91
91
  socket: net.Socket
92
92
  srcId: number
93
93
  dstId: number
@@ -95,7 +95,7 @@ export type SocketChannel = {
95
95
  dstChannel: number
96
96
  notify: (size: number) => void
97
97
  recvPackSize: number
98
- key_iv: [CryptoKey, Uint8Array]
98
+ key_iv: [CryptoKey, Uint8Array<ArrayBuffer>]
99
99
  }
100
100
 
101
101