appium-ios-tuntap 0.0.1
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/CHANGELOG.md +83 -0
- package/LICENSE +201 -0
- package/README.md +256 -0
- package/binding.gyp +84 -0
- package/build/Makefile +352 -0
- package/build/Release/.deps/Release/nothing.a.d +1 -0
- package/build/Release/.deps/Release/obj.target/nothing/node_modules/node-addon-api/nothing.o.d +4 -0
- package/build/Release/.deps/Release/obj.target/tuntap/src/tuntap.o.d +17 -0
- package/build/Release/.deps/Release/tuntap.node.d +1 -0
- package/build/Release/nothing.a +0 -0
- package/build/Release/obj.target/nothing/node_modules/node-addon-api/nothing.o +0 -0
- package/build/Release/obj.target/tuntap/src/tuntap.o +0 -0
- package/build/Release/tuntap.node +0 -0
- package/build/binding.Makefile +6 -0
- package/build/config.gypi +441 -0
- package/build/gyp-mac-tool +768 -0
- package/build/node_modules/node-addon-api/node_api.Makefile +6 -0
- package/build/node_modules/node-addon-api/nothing.target.mk +185 -0
- package/build/tuntap.target.mk +222 -0
- package/lib/TunTap.d.ts +40 -0
- package/lib/TunTap.js +347 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/logger.d.ts +1 -0
- package/lib/logger.js +9 -0
- package/lib/tunnel.d.ts +60 -0
- package/lib/tunnel.js +486 -0
- package/package.json +49 -0
- package/src/tuntap.cc +501 -0
- package/test/check-linux-prereqs.sh +96 -0
- package/test/test-tuntap.js +150 -0
package/lib/tunnel.js
ADDED
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import { log } from './logger.js';
|
|
2
|
+
import { TunTap } from './TunTap.js';
|
|
3
|
+
import { EventEmitter } from 'events';
|
|
4
|
+
import { Buffer } from 'buffer';
|
|
5
|
+
// Global registry for active tunnel managers
|
|
6
|
+
const activeTunnelManagers = new Set();
|
|
7
|
+
// Setup process signal handlers
|
|
8
|
+
let signalHandlersSetup = false;
|
|
9
|
+
function setupSignalHandlers() {
|
|
10
|
+
if (signalHandlersSetup)
|
|
11
|
+
return;
|
|
12
|
+
signalHandlersSetup = true;
|
|
13
|
+
const gracefulShutdown = async (signal) => {
|
|
14
|
+
log(`Received ${signal}, initiating graceful shutdown...`);
|
|
15
|
+
// Copy the set to avoid modification during iteration
|
|
16
|
+
const managers = Array.from(activeTunnelManagers);
|
|
17
|
+
// Stop all tunnel managers
|
|
18
|
+
await Promise.all(managers.map(manager => {
|
|
19
|
+
try {
|
|
20
|
+
return manager.stop();
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
console.error('Error stopping tunnel manager:', err);
|
|
24
|
+
}
|
|
25
|
+
}));
|
|
26
|
+
log('All tunnel managers stopped, exiting...');
|
|
27
|
+
process.exit(0);
|
|
28
|
+
};
|
|
29
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
30
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
31
|
+
// Handle uncaught exceptions
|
|
32
|
+
process.on('uncaughtException', (err) => {
|
|
33
|
+
console.error('Uncaught exception:', err);
|
|
34
|
+
gracefulShutdown('uncaughtException').then(() => process.exit(1));
|
|
35
|
+
});
|
|
36
|
+
// Handle unhandled promise rejections
|
|
37
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
38
|
+
console.error('Unhandled rejection at:', promise, 'reason:', reason);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export class TunnelManager extends EventEmitter {
|
|
42
|
+
tun;
|
|
43
|
+
cancelled;
|
|
44
|
+
readInterval;
|
|
45
|
+
buffer;
|
|
46
|
+
packetConsumers;
|
|
47
|
+
packetQueue;
|
|
48
|
+
deviceConn;
|
|
49
|
+
cleanupPromise;
|
|
50
|
+
constructor() {
|
|
51
|
+
super();
|
|
52
|
+
this.tun = null;
|
|
53
|
+
this.cancelled = false;
|
|
54
|
+
this.readInterval = null;
|
|
55
|
+
this.buffer = Buffer.alloc(0);
|
|
56
|
+
this.packetConsumers = new Set();
|
|
57
|
+
this.packetQueue = [];
|
|
58
|
+
this.deviceConn = null;
|
|
59
|
+
this.cleanupPromise = null;
|
|
60
|
+
// Setup signal handlers on first tunnel manager creation
|
|
61
|
+
setupSignalHandlers();
|
|
62
|
+
// Register this manager
|
|
63
|
+
activeTunnelManagers.add(this);
|
|
64
|
+
}
|
|
65
|
+
addPacketConsumer(consumer) {
|
|
66
|
+
this.packetConsumers.add(consumer);
|
|
67
|
+
}
|
|
68
|
+
removePacketConsumer(consumer) {
|
|
69
|
+
this.packetConsumers.delete(consumer);
|
|
70
|
+
}
|
|
71
|
+
async *getPacketStream() {
|
|
72
|
+
const queue = [];
|
|
73
|
+
let resolver = null;
|
|
74
|
+
const consumer = {
|
|
75
|
+
onPacket: (packet) => {
|
|
76
|
+
if (resolver) {
|
|
77
|
+
resolver({ value: packet, done: false });
|
|
78
|
+
resolver = null;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
queue.push(packet);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
this.addPacketConsumer(consumer);
|
|
86
|
+
try {
|
|
87
|
+
while (!this.cancelled) {
|
|
88
|
+
if (queue.length > 0) {
|
|
89
|
+
yield queue.shift();
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
yield await new Promise((resolve) => {
|
|
93
|
+
resolver = (result) => {
|
|
94
|
+
if (!result.done) {
|
|
95
|
+
resolve(result.value);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
this.removePacketConsumer(consumer);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async setupInterface(tunnelInfo) {
|
|
107
|
+
log(`Setting up tunnel with parameters:`, tunnelInfo);
|
|
108
|
+
try {
|
|
109
|
+
this.tun = new TunTap();
|
|
110
|
+
// Open the TUN device
|
|
111
|
+
if (!this.tun.open()) {
|
|
112
|
+
throw new Error("Failed to open TUN device");
|
|
113
|
+
}
|
|
114
|
+
log(`Opened TUN device: ${this.tun.name}`);
|
|
115
|
+
// Configure the TUN device with IPv6 address and MTU
|
|
116
|
+
await this.tun.configure(tunnelInfo.clientParameters.address, tunnelInfo.clientParameters.mtu);
|
|
117
|
+
// Add route for the server address
|
|
118
|
+
await this.tun.addRoute(`${tunnelInfo.serverAddress}/128`);
|
|
119
|
+
log(`Configured TUN interface ${this.tun.name} with address ${tunnelInfo.clientParameters.address} and MTU ${tunnelInfo.clientParameters.mtu}`);
|
|
120
|
+
return {
|
|
121
|
+
name: this.tun.name,
|
|
122
|
+
mtu: tunnelInfo.clientParameters.mtu,
|
|
123
|
+
interface: this.tun
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
console.error(`Error setting up TUN interface: ${err.message}`);
|
|
128
|
+
if (this.tun) {
|
|
129
|
+
try {
|
|
130
|
+
this.tun.close();
|
|
131
|
+
}
|
|
132
|
+
catch (closeErr) {
|
|
133
|
+
console.error('Error closing TUN device:', closeErr);
|
|
134
|
+
}
|
|
135
|
+
this.tun = null;
|
|
136
|
+
}
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
startForwarding(deviceConn) {
|
|
141
|
+
if (!this.tun) {
|
|
142
|
+
console.error("TUN device is not set up");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
this.deviceConn = deviceConn;
|
|
146
|
+
log(`Starting bidirectional data forwarding for ${this.tun.name}`);
|
|
147
|
+
// Handle data from the device connection
|
|
148
|
+
deviceConn.on('data', (data) => {
|
|
149
|
+
if (this.cancelled)
|
|
150
|
+
return;
|
|
151
|
+
try {
|
|
152
|
+
// Add data to buffer
|
|
153
|
+
this.buffer = Buffer.concat([this.buffer, data]);
|
|
154
|
+
// Process IPv6 packets
|
|
155
|
+
this.processBuffer();
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
if (!this.cancelled) {
|
|
159
|
+
console.error('Error processing device data:', err.message);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
// Set up TUN read loop
|
|
164
|
+
this.startTunReadLoop(deviceConn);
|
|
165
|
+
// Listen for device connection close
|
|
166
|
+
deviceConn.on('close', () => {
|
|
167
|
+
log('Device connection closed, stopping tunnel');
|
|
168
|
+
this.stop().catch(err => console.error('Error stopping tunnel:', err));
|
|
169
|
+
});
|
|
170
|
+
deviceConn.on('error', (err) => {
|
|
171
|
+
console.error('Device connection error:', err.message);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
processBuffer() {
|
|
175
|
+
let offset = 0;
|
|
176
|
+
// Process as many complete packets as available
|
|
177
|
+
while (offset + 40 <= this.buffer.length) {
|
|
178
|
+
// Extract IPv6 header (fixed 40 bytes)
|
|
179
|
+
const header = this.buffer.slice(offset, offset + 40);
|
|
180
|
+
// Ensure this is an IPv6 packet (version 6)
|
|
181
|
+
const version = (header[0] >> 4) & 0x0F;
|
|
182
|
+
if (version !== 6) {
|
|
183
|
+
offset++;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
// Get payload length from the IPv6 header
|
|
187
|
+
const payloadLength = header.readUInt16BE(4);
|
|
188
|
+
// Ensure we have the full packet (IPv6 header + payload)
|
|
189
|
+
if (offset + 40 + payloadLength > this.buffer.length) {
|
|
190
|
+
break; // Wait for more data
|
|
191
|
+
}
|
|
192
|
+
// Extract the complete IPv6 packet
|
|
193
|
+
const packet = this.buffer.slice(offset, offset + 40 + payloadLength);
|
|
194
|
+
// Extract source and destination IPv6 addresses
|
|
195
|
+
const src = formatIPv6Address(packet.slice(8, 24));
|
|
196
|
+
const dst = formatIPv6Address(packet.slice(24, 40));
|
|
197
|
+
// Get the IPv6 next header value
|
|
198
|
+
const nextHeader = header[6];
|
|
199
|
+
log(`Processing packet: nextHeader=${nextHeader}, totalLength=${40 + payloadLength}`);
|
|
200
|
+
try {
|
|
201
|
+
if (!this.tun) {
|
|
202
|
+
console.error('TUN device is null during packet processing');
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
const bytesWritten = this.tun.write(packet);
|
|
206
|
+
log(`Device → TUN: ${bytesWritten} bytes, IPv6 src=${src}, dst=${dst}`);
|
|
207
|
+
// Handle UDP packets (nextHeader === 17)
|
|
208
|
+
if (nextHeader === 17) {
|
|
209
|
+
const payload = packet.slice(40);
|
|
210
|
+
log(`UDP packet detected: payload length=${payload.length}`);
|
|
211
|
+
if (payload.length < 8) {
|
|
212
|
+
log("UDP payload too short, not emitting event.");
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
const sourcePort = payload.readUInt16BE(0);
|
|
216
|
+
const destPort = payload.readUInt16BE(2);
|
|
217
|
+
const udpPayload = payload.slice(8);
|
|
218
|
+
const packetData = {
|
|
219
|
+
protocol: 'UDP',
|
|
220
|
+
src,
|
|
221
|
+
dst,
|
|
222
|
+
sourcePort,
|
|
223
|
+
destPort,
|
|
224
|
+
payload: udpPayload
|
|
225
|
+
};
|
|
226
|
+
this.emit('data', packetData);
|
|
227
|
+
this.packetConsumers.forEach(consumer => {
|
|
228
|
+
try {
|
|
229
|
+
consumer.onPacket(packetData);
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
console.error('Error in packet consumer:', err);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
log('Emitted data event for UDP packet');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Handle TCP packets (nextHeader === 6)
|
|
239
|
+
else if (nextHeader === 6) {
|
|
240
|
+
const tcpHeaderStart = 40;
|
|
241
|
+
if (packet.length < tcpHeaderStart + 20) {
|
|
242
|
+
log("TCP packet too short for minimum header, skipping.");
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
const sourcePort = packet.readUInt16BE(tcpHeaderStart);
|
|
246
|
+
const destPort = packet.readUInt16BE(tcpHeaderStart + 2);
|
|
247
|
+
const dataOffsetByte = packet.readUInt8(tcpHeaderStart + 12);
|
|
248
|
+
const tcpHeaderLength = (dataOffsetByte >> 4) * 4;
|
|
249
|
+
if (packet.length < tcpHeaderStart + tcpHeaderLength) {
|
|
250
|
+
log("TCP header length exceeds packet length, skipping.");
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
const tcpPayload = packet.slice(tcpHeaderStart + tcpHeaderLength);
|
|
254
|
+
log(`TCP packet detected: headerLength=${tcpHeaderLength}, payload length=${tcpPayload.length}`);
|
|
255
|
+
const packetData = {
|
|
256
|
+
protocol: 'TCP',
|
|
257
|
+
src,
|
|
258
|
+
dst,
|
|
259
|
+
sourcePort,
|
|
260
|
+
destPort,
|
|
261
|
+
payload: tcpPayload
|
|
262
|
+
};
|
|
263
|
+
this.emit('data', packetData);
|
|
264
|
+
this.packetConsumers.forEach(consumer => {
|
|
265
|
+
try {
|
|
266
|
+
consumer.onPacket(packetData);
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
console.error('Error in packet consumer:', err);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
log('Emitted data event for TCP packet');
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
log("Packet is not UDP or TCP (nextHeader !== 17 and !== 6)");
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
console.error(`Error writing to TUN: ${err.message}`);
|
|
282
|
+
}
|
|
283
|
+
// Move to the next packet
|
|
284
|
+
offset += 40 + payloadLength;
|
|
285
|
+
}
|
|
286
|
+
// Keep any remaining partial data
|
|
287
|
+
if (offset > 0) {
|
|
288
|
+
this.buffer = this.buffer.slice(offset);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
startTunReadLoop(deviceConn) {
|
|
292
|
+
this.readInterval = setInterval(() => {
|
|
293
|
+
if (this.cancelled || !this.tun)
|
|
294
|
+
return;
|
|
295
|
+
try {
|
|
296
|
+
// Read from TUN
|
|
297
|
+
const data = this.tun.read(16384); // A large buffer for MTU
|
|
298
|
+
// If we got data, send it to the device
|
|
299
|
+
if (data && data.length > 0) {
|
|
300
|
+
if (data.length >= 40) { // Minimum IPv6 header size
|
|
301
|
+
log(`TUN → Device: ${data.length} bytes, IPv6 src=${formatIPv6Address(data.slice(8, 24))}, dst=${formatIPv6Address(data.slice(24, 40))}`);
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
log(`TUN → Device: ${data.length} bytes (too small for IPv6 header)`);
|
|
305
|
+
}
|
|
306
|
+
if (!deviceConn.destroyed) {
|
|
307
|
+
deviceConn.write(data);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
catch (err) {
|
|
312
|
+
if (!this.cancelled) {
|
|
313
|
+
console.error('Error reading from TUN:', err.message);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}, 5); // Poll every 5ms
|
|
317
|
+
}
|
|
318
|
+
async stop() {
|
|
319
|
+
// Prevent multiple concurrent stops
|
|
320
|
+
if (this.cleanupPromise) {
|
|
321
|
+
return this.cleanupPromise;
|
|
322
|
+
}
|
|
323
|
+
this.cleanupPromise = this._performStop();
|
|
324
|
+
return this.cleanupPromise;
|
|
325
|
+
}
|
|
326
|
+
async _performStop() {
|
|
327
|
+
const tunName = this.tun ? this.tun.name : 'unknown';
|
|
328
|
+
log(`Stopping tunnel manager for ${tunName}`);
|
|
329
|
+
// Signal cancellation
|
|
330
|
+
this.cancelled = true;
|
|
331
|
+
// Clear read interval
|
|
332
|
+
if (this.readInterval) {
|
|
333
|
+
clearInterval(this.readInterval);
|
|
334
|
+
this.readInterval = null;
|
|
335
|
+
}
|
|
336
|
+
// Close device connection if exists
|
|
337
|
+
if (this.deviceConn && !this.deviceConn.destroyed) {
|
|
338
|
+
this.deviceConn.destroy();
|
|
339
|
+
this.deviceConn = null;
|
|
340
|
+
}
|
|
341
|
+
// Clear buffer
|
|
342
|
+
this.buffer = Buffer.alloc(0);
|
|
343
|
+
// Clear packet consumers
|
|
344
|
+
this.packetConsumers.clear();
|
|
345
|
+
// Remove all listeners
|
|
346
|
+
this.removeAllListeners();
|
|
347
|
+
// Close TUN device
|
|
348
|
+
if (this.tun) {
|
|
349
|
+
try {
|
|
350
|
+
this.tun.close();
|
|
351
|
+
}
|
|
352
|
+
catch (err) {
|
|
353
|
+
console.error('Error closing TUN device:', err);
|
|
354
|
+
}
|
|
355
|
+
this.tun = null;
|
|
356
|
+
}
|
|
357
|
+
// Unregister from active managers
|
|
358
|
+
activeTunnelManagers.delete(this);
|
|
359
|
+
log(`Tunnel for ${tunName} closed successfully`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
function formatIPv6Address(buffer) {
|
|
363
|
+
if (!buffer || buffer.length !== 16) {
|
|
364
|
+
return 'invalid-address';
|
|
365
|
+
}
|
|
366
|
+
const parts = [];
|
|
367
|
+
for (let i = 0; i < 16; i += 2) {
|
|
368
|
+
parts.push(buffer.readUInt16BE(i).toString(16));
|
|
369
|
+
}
|
|
370
|
+
return parts.join(':');
|
|
371
|
+
}
|
|
372
|
+
export async function exchangeCoreTunnelParameters(socket) {
|
|
373
|
+
return new Promise((resolve, reject) => {
|
|
374
|
+
const request = {
|
|
375
|
+
type: "clientHandshakeRequest",
|
|
376
|
+
mtu: 16000
|
|
377
|
+
};
|
|
378
|
+
const requestJSON = JSON.stringify(request);
|
|
379
|
+
const jsonBuffer = Buffer.from(requestJSON);
|
|
380
|
+
const magic = Buffer.from('CDTunnel');
|
|
381
|
+
const length = Buffer.alloc(2);
|
|
382
|
+
length.writeUInt16BE(jsonBuffer.length);
|
|
383
|
+
const message = Buffer.concat([magic, length, jsonBuffer]);
|
|
384
|
+
log(`Sending CDTunnel packet: magic=${magic.toString()}, length=${jsonBuffer.length}, body=${requestJSON}`);
|
|
385
|
+
socket.write(message);
|
|
386
|
+
// For receiving the response
|
|
387
|
+
let buffer = Buffer.alloc(0);
|
|
388
|
+
let timeoutHandle;
|
|
389
|
+
function cleanup() {
|
|
390
|
+
socket.removeListener('data', handleData);
|
|
391
|
+
socket.removeListener('error', handleError);
|
|
392
|
+
socket.removeListener('end', handleEnd);
|
|
393
|
+
if (timeoutHandle) {
|
|
394
|
+
clearTimeout(timeoutHandle);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function handleData(data) {
|
|
398
|
+
log("Received data chunk:", data.length, "bytes");
|
|
399
|
+
buffer = Buffer.concat([buffer, data]);
|
|
400
|
+
if (buffer.length < 10)
|
|
401
|
+
return;
|
|
402
|
+
const receivedMagic = buffer.slice(0, 8).toString();
|
|
403
|
+
if (receivedMagic !== 'CDTunnel') {
|
|
404
|
+
console.error("Invalid magic header:", receivedMagic);
|
|
405
|
+
cleanup();
|
|
406
|
+
return reject(new Error("Invalid packet format"));
|
|
407
|
+
}
|
|
408
|
+
const payloadLength = buffer.readUInt16BE(8);
|
|
409
|
+
const totalLength = 8 + 2 + payloadLength;
|
|
410
|
+
log("Expected total packet length:", totalLength, "current buffer:", buffer.length);
|
|
411
|
+
if (buffer.length >= totalLength) {
|
|
412
|
+
const payload = buffer.slice(10, totalLength);
|
|
413
|
+
try {
|
|
414
|
+
const response = JSON.parse(payload.toString());
|
|
415
|
+
log("Parsed CDTunnel response:", response);
|
|
416
|
+
cleanup();
|
|
417
|
+
resolve(response);
|
|
418
|
+
}
|
|
419
|
+
catch (err) {
|
|
420
|
+
console.error("Failed to parse JSON:", err);
|
|
421
|
+
cleanup();
|
|
422
|
+
reject(new Error("Invalid JSON response"));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function handleError(err) {
|
|
427
|
+
console.error("Socket error:", err);
|
|
428
|
+
cleanup();
|
|
429
|
+
reject(err);
|
|
430
|
+
}
|
|
431
|
+
function handleEnd() {
|
|
432
|
+
log("Connection ended");
|
|
433
|
+
if (buffer.length > 0) {
|
|
434
|
+
log("Buffer at end:", buffer.toString('hex'));
|
|
435
|
+
}
|
|
436
|
+
cleanup();
|
|
437
|
+
reject(new Error("Connection closed before receiving complete response"));
|
|
438
|
+
}
|
|
439
|
+
// Set a timeout for the handshake
|
|
440
|
+
timeoutHandle = setTimeout(() => {
|
|
441
|
+
cleanup();
|
|
442
|
+
reject(new Error("Tunnel handshake timeout"));
|
|
443
|
+
}, 30000); // 30 second timeout
|
|
444
|
+
socket.on('data', handleData);
|
|
445
|
+
socket.on('error', handleError);
|
|
446
|
+
socket.on('end', handleEnd);
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
export async function connectToTunnelLockdown(secureServiceSocket) {
|
|
450
|
+
const tunnelManager = new TunnelManager();
|
|
451
|
+
try {
|
|
452
|
+
// Exchange tunnel parameters with the device
|
|
453
|
+
const tunnelInfo = await exchangeCoreTunnelParameters(secureServiceSocket);
|
|
454
|
+
log("Tunnel parameters exchanged:", tunnelInfo);
|
|
455
|
+
// Setup tunnel interface
|
|
456
|
+
const tunInterfaceInfo = await tunnelManager.setupInterface(tunnelInfo);
|
|
457
|
+
log("Tunnel interface set up:", tunInterfaceInfo.name);
|
|
458
|
+
// Start bidirectional forwarding
|
|
459
|
+
tunnelManager.startForwarding(secureServiceSocket);
|
|
460
|
+
// Create close function
|
|
461
|
+
const closeFunc = async () => {
|
|
462
|
+
log("Closing tunnel connection");
|
|
463
|
+
await tunnelManager.stop();
|
|
464
|
+
if (!secureServiceSocket.destroyed) {
|
|
465
|
+
secureServiceSocket.end();
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
return {
|
|
469
|
+
Address: tunnelInfo.serverAddress,
|
|
470
|
+
RsdPort: tunnelInfo.serverRSDPort,
|
|
471
|
+
tunnelManager: tunnelManager,
|
|
472
|
+
closer: closeFunc,
|
|
473
|
+
addPacketConsumer: (consumer) => tunnelManager.addPacketConsumer(consumer),
|
|
474
|
+
removePacketConsumer: (consumer) => tunnelManager.removePacketConsumer(consumer),
|
|
475
|
+
getPacketStream: () => tunnelManager.getPacketStream()
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
catch (err) {
|
|
479
|
+
console.error("Failed to connect to tunnel:", err);
|
|
480
|
+
await tunnelManager.stop();
|
|
481
|
+
if (!secureServiceSocket.destroyed) {
|
|
482
|
+
secureServiceSocket.end();
|
|
483
|
+
}
|
|
484
|
+
throw err;
|
|
485
|
+
}
|
|
486
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "appium-ios-tuntap",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Native TUN/TAP interface module for Node.js",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"clean": "rm -rf build node_modules lib",
|
|
10
|
+
"build": "npx tsc",
|
|
11
|
+
"build:addon": "node-gyp rebuild",
|
|
12
|
+
"prepare": "npm run build:addon && npm run build",
|
|
13
|
+
"test": "sudo node test/test-tuntap.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src/tuntap.cc",
|
|
17
|
+
"lib",
|
|
18
|
+
"build",
|
|
19
|
+
"binding.gyp",
|
|
20
|
+
"package.json",
|
|
21
|
+
"test",
|
|
22
|
+
"README.md",
|
|
23
|
+
"CHANGELOG.md"
|
|
24
|
+
],
|
|
25
|
+
"author": {
|
|
26
|
+
"name": "Sri Harsha",
|
|
27
|
+
"url": "https://github.com/harsha509"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/appium/appium-ios-tuntap.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/appium/appium-ios-tuntap/issues"
|
|
35
|
+
},
|
|
36
|
+
"license": "Apache-2.0",
|
|
37
|
+
"gypfile": true,
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"node-addon-api": "^8.3.1",
|
|
40
|
+
"typescript": "^5.8.3"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
44
|
+
"@semantic-release/git": "^10.0.1",
|
|
45
|
+
"@types/node": "^22.15.30",
|
|
46
|
+
"conventional-changelog-conventionalcommits": "^9.0.0",
|
|
47
|
+
"semantic-release": "^24.0.0"
|
|
48
|
+
}
|
|
49
|
+
}
|