appium-ios-tuntap 0.4.3 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [0.5.0](https://github.com/appium/appium-ios-tuntap/compare/v0.4.4...v0.5.0) (2026-06-13)
2
+
3
+ ### Features
4
+
5
+ * Switch to a different tunnelling architecture ([#51](https://github.com/appium/appium-ios-tuntap/issues/51)) ([7267755](https://github.com/appium/appium-ios-tuntap/commit/726775526d50d1976192bf3ebfbae6cb81883631))
6
+
7
+ ## [0.4.4](https://github.com/appium/appium-ios-tuntap/compare/v0.4.3...v0.4.4) (2026-06-12)
8
+
9
+ ### Bug Fixes
10
+
11
+ * Update tunnel backpressure handling ([#50](https://github.com/appium/appium-ios-tuntap/issues/50)) ([28b8bce](https://github.com/appium/appium-ios-tuntap/commit/28b8bce8edcf2a0446a653bbec759e6b4a199827))
12
+
1
13
  ## [0.4.3](https://github.com/appium/appium-ios-tuntap/compare/v0.4.2...v0.4.3) (2026-06-10)
2
14
 
3
15
  ### Bug Fixes
package/README.md CHANGED
@@ -272,13 +272,13 @@ Most tests for this module require **root privileges** (sudo) to create and mana
272
272
  From the project root, run:
273
273
 
274
274
  ```sh
275
- sudo npx mocha test/tuntap-unit.spec.js
275
+ sudo npm run test:unit
276
276
  ```
277
277
 
278
- Or, to run all tests in the `test/` directory:
278
+ Or, to run all tests:
279
279
 
280
280
  ```sh
281
- sudo npx mocha
281
+ sudo npm test
282
282
  ```
283
283
 
284
284
  If you are **not** running as root, you will see a message that tests are skipped.
@@ -1,5 +1,6 @@
1
1
  import { TunTapError } from '../errors.js';
2
2
  import { log } from '../logger.js';
3
+ import { tunDebug } from '../tunnel/debug-log.js';
3
4
  import { assertAdminOnWindows } from './require-admin.js';
4
5
  import { execFileAsync } from './exec.js';
5
6
  /** Tightly-restricted character set for adapter names passed into PowerShell. */
@@ -19,7 +20,7 @@ export class WindowsTunTapPlatform {
19
20
  async configure(interfaceName, address, mtu) {
20
21
  await assertAdminOnWindows();
21
22
  assertSafeAdapterName(interfaceName);
22
- log.debug(`[win] configure: interface=${interfaceName} address=${address} mtu=${mtu}`);
23
+ tunDebug(`[win] configure: interface=${interfaceName} address=${address} mtu=${mtu}`);
23
24
  await addIpv6Address(interfaceName, address);
24
25
  await setIpv6Mtu(interfaceName, mtu);
25
26
  }
@@ -27,7 +28,7 @@ export class WindowsTunTapPlatform {
27
28
  async addRoute(interfaceName, destination) {
28
29
  await assertAdminOnWindows();
29
30
  assertSafeAdapterName(interfaceName);
30
- log.debug(`[win] addRoute: interface=${interfaceName} destination=${destination}`);
31
+ tunDebug(`[win] addRoute: interface=${interfaceName} destination=${destination}`);
31
32
  await addIpv6Route(interfaceName, destination);
32
33
  // WinTun presents as an Ethernet adapter, so Windows requires Neighbor
33
34
  // Discovery (NDP) before it will send packets through the interface.
@@ -102,7 +103,7 @@ async function addIpv6Address(interfaceName, address) {
102
103
  `address=${address}/64`,
103
104
  'store=active',
104
105
  ]);
105
- log.debug(`[win] add address ok: ${r.stdout.trim() || '(no output)'}`);
106
+ tunDebug(`[win] add address ok: ${r.stdout.trim() || '(no output)'}`);
106
107
  }
107
108
  catch (err) {
108
109
  const message = err.message ?? '';
@@ -124,7 +125,7 @@ async function setIpv6Mtu(interfaceName, mtu) {
124
125
  `mtu=${mtu}`,
125
126
  'store=active',
126
127
  ]);
127
- log.debug(`[win] set mtu ok: ${r.stdout.trim() || '(no output)'}`);
128
+ tunDebug(`[win] set mtu ok: ${r.stdout.trim() || '(no output)'}`);
128
129
  }
129
130
  catch (err) {
130
131
  log.warn(`[win] set mtu err: ${err.message ?? err}`);
@@ -142,13 +143,13 @@ async function addIpv6Route(interfaceName, destination) {
142
143
  interfaceName,
143
144
  'store=active',
144
145
  ]);
145
- log.debug(`[win] add route ok: ${r.stdout.trim() || '(no output)'}`);
146
+ tunDebug(`[win] add route ok: ${r.stdout.trim() || '(no output)'}`);
146
147
  }
147
148
  catch (err) {
148
149
  const message = err.message ?? '';
149
150
  log.warn(`[win] add route err: ${message}`);
150
151
  if (/already exists|object already/i.test(message)) {
151
- log.debug(`Route to ${destination} already exists`);
152
+ tunDebug(`Route to ${destination} already exists`);
152
153
  return;
153
154
  }
154
155
  throw err;
@@ -174,7 +175,7 @@ async function deleteIpv6Route(interfaceName, destination) {
174
175
  }
175
176
  }
176
177
  async function addStaticNeighbor(interfaceName, address) {
177
- log.debug(`[win] addStaticNeighbor: interface=${interfaceName} address=${address}`);
178
+ tunDebug(`[win] addStaticNeighbor: interface=${interfaceName} address=${address}`);
178
179
  try {
179
180
  const r = await execFileAsync('netsh', [
180
181
  'interface',
@@ -186,7 +187,7 @@ async function addStaticNeighbor(interfaceName, address) {
186
187
  '00-00-00-00-00-01',
187
188
  'store=active',
188
189
  ]);
189
- log.debug(`[win] add neighbor ok: ${r.stdout.trim() || '(no output)'}`);
190
+ tunDebug(`[win] add neighbor ok: ${r.stdout.trim() || '(no output)'}`);
190
191
  }
191
192
  catch (err) {
192
193
  const msg = err.message ?? String(err);
@@ -6,12 +6,22 @@ export declare const MAX_TUN_POLL_BUFFER = 65535;
6
6
  export declare const LARGE_TUN_POLL_BUFFER: number;
7
7
  /** Default ThreadSafeFunction queue depth for TUN polling. */
8
8
  export declare const DEFAULT_TUN_POLL_QUEUE_DEPTH = 8;
9
- /** Deeper TSFN queue when packet tap is off (bulk transfer). */
10
- export declare const FAST_TUN_POLL_QUEUE_DEPTH = 16;
9
+ /** One in-flight TUN read for sequential pump forwarding. */
10
+ export declare const SEQUENTIAL_TUN_POLL_QUEUE_DEPTH = 1;
11
+ /** Max utun packets buffered on the JS side before TLS write. */
12
+ export declare const MAX_TUN_INGRESS_QUEUE = 256;
13
+ /** Max ThreadSafeFunction queue depth (native addon limit). */
14
+ export declare const MAX_TUN_POLL_TSFN_QUEUE_DEPTH = 64;
15
+ /** ThreadSafeFunction queue depth passed to {@link TunTap.startPolling}. */
16
+ export declare const TUN_POLL_TSFN_QUEUE_DEPTH = 64;
17
+ /** Yield device→TUN loop periodically so utun poll callbacks can run. */
18
+ export declare const DEVICE_PUMP_YIELD_EVERY_FRAMES = 64;
11
19
  export declare const CD_TUNNEL_MAGIC = "CDTunnel";
12
20
  export declare const CD_TUNNEL_MAGIC_SIZE = 8;
13
21
  export declare const CD_TUNNEL_HEADER_SIZE: number;
14
22
  export declare const CD_TUNNEL_HANDSHAKE_TIMEOUT_MS = 30000;
23
+ /** Pause device ingress when the reassembly buffer exceeds this size. */
24
+ export declare const MAX_DEVICE_INGRESS_BUFFER: number;
15
25
  export declare const IPV6_HEADER_SIZE = 40;
16
26
  export declare const IPV6_VERSION = 6;
17
27
  export declare const IPPROTO_TCP = 6;
@@ -6,12 +6,22 @@ export const MAX_TUN_POLL_BUFFER = 65_535;
6
6
  export const LARGE_TUN_POLL_BUFFER = 64 * 1024;
7
7
  /** Default ThreadSafeFunction queue depth for TUN polling. */
8
8
  export const DEFAULT_TUN_POLL_QUEUE_DEPTH = 8;
9
- /** Deeper TSFN queue when packet tap is off (bulk transfer). */
10
- export const FAST_TUN_POLL_QUEUE_DEPTH = 16;
9
+ /** One in-flight TUN read for sequential pump forwarding. */
10
+ export const SEQUENTIAL_TUN_POLL_QUEUE_DEPTH = 1;
11
+ /** Max utun packets buffered on the JS side before TLS write. */
12
+ export const MAX_TUN_INGRESS_QUEUE = 256;
13
+ /** Max ThreadSafeFunction queue depth (native addon limit). */
14
+ export const MAX_TUN_POLL_TSFN_QUEUE_DEPTH = 64;
15
+ /** ThreadSafeFunction queue depth passed to {@link TunTap.startPolling}. */
16
+ export const TUN_POLL_TSFN_QUEUE_DEPTH = MAX_TUN_POLL_TSFN_QUEUE_DEPTH;
17
+ /** Yield device→TUN loop periodically so utun poll callbacks can run. */
18
+ export const DEVICE_PUMP_YIELD_EVERY_FRAMES = 64;
11
19
  export const CD_TUNNEL_MAGIC = 'CDTunnel';
12
20
  export const CD_TUNNEL_MAGIC_SIZE = 8;
13
21
  export const CD_TUNNEL_HEADER_SIZE = CD_TUNNEL_MAGIC_SIZE + 2;
14
22
  export const CD_TUNNEL_HANDSHAKE_TIMEOUT_MS = 30_000;
23
+ /** Pause device ingress when the reassembly buffer exceeds this size. */
24
+ export const MAX_DEVICE_INGRESS_BUFFER = 8 * 1024 * 1024;
15
25
  export const IPV6_HEADER_SIZE = 40;
16
26
  export const IPV6_VERSION = 6;
17
27
  export const IPPROTO_TCP = 6;
@@ -0,0 +1,12 @@
1
+ import { log } from '../logger.js';
2
+ /** Tunnel debug logging — set APPIUM_TUNTAP_DEBUG=1 on the tunnel process. */
3
+ export declare const APPIUM_TUNTAP_DEBUG: boolean;
4
+ /** {@link log.debug} when {@link APPIUM_TUNTAP_DEBUG} is enabled. */
5
+ export declare function tunDebug(...args: Parameters<typeof log.debug>): void;
6
+ /** Log a numbered tunnel forward diagnostic when {@link APPIUM_TUNTAP_DEBUG} is enabled. */
7
+ export declare function fwdDebug(event: string, detail?: Record<string, string | number | boolean>): void;
8
+ /** Summarize reassembly buffer state for debug logs. */
9
+ export declare function fwdBufferState(buffer: Buffer): {
10
+ buf: number;
11
+ tailKind: string;
12
+ };
@@ -0,0 +1,44 @@
1
+ import { log } from '../logger.js';
2
+ /** Tunnel debug logging — set APPIUM_TUNTAP_DEBUG=1 on the tunnel process. */
3
+ export const APPIUM_TUNTAP_DEBUG = process.env.APPIUM_TUNTAP_DEBUG === '1' || process.env.APPIUM_TUNTAP_DEBUG === 'true';
4
+ let seq = 0;
5
+ /** {@link log.debug} when {@link APPIUM_TUNTAP_DEBUG} is enabled. */
6
+ export function tunDebug(...args) {
7
+ if (!APPIUM_TUNTAP_DEBUG) {
8
+ return;
9
+ }
10
+ log.debug(...args);
11
+ }
12
+ /** Log a numbered tunnel forward diagnostic when {@link APPIUM_TUNTAP_DEBUG} is enabled. */
13
+ export function fwdDebug(event, detail) {
14
+ if (!APPIUM_TUNTAP_DEBUG) {
15
+ return;
16
+ }
17
+ seq += 1;
18
+ const parts = [`#${seq}`, event];
19
+ if (detail) {
20
+ for (const [key, value] of Object.entries(detail)) {
21
+ parts.push(`${key}=${value}`);
22
+ }
23
+ }
24
+ log.info(`[fwd] ${parts.join(' ')}`);
25
+ }
26
+ /** Summarize reassembly buffer state for debug logs. */
27
+ export function fwdBufferState(buffer) {
28
+ if (buffer.length === 0) {
29
+ return { buf: 0, tailKind: 'empty' };
30
+ }
31
+ if (buffer.length < 40) {
32
+ return { buf: buffer.length, tailKind: 'short' };
33
+ }
34
+ const version = (buffer[0] >> 4) & 0x0f;
35
+ if (version !== 6) {
36
+ return { buf: buffer.length, tailKind: 'resync-needed' };
37
+ }
38
+ const payloadLength = buffer.readUInt16BE(4);
39
+ const frameLength = 40 + payloadLength;
40
+ if (buffer.length >= frameLength) {
41
+ return { buf: buffer.length, tailKind: 'has-complete-frame' };
42
+ }
43
+ return { buf: buffer.length, tailKind: 'incomplete-tail' };
44
+ }
@@ -0,0 +1,36 @@
1
+ import type { Socket } from 'node:net';
2
+ import type { TunTap } from '../TunTap.js';
3
+ export type DeviceToTunProgressHook = () => void;
4
+ /**
5
+ * Device→TUN forwarding: read one exact IPv6 frame from the socket, write to TUN,
6
+ * repeat. Yields only when TUN write blocks (via notifyTunWritable from the TUN→device pump).
7
+ */
8
+ export declare class DeviceToTunPump {
9
+ private readonly onFrameWritten?;
10
+ private cancelled;
11
+ private running;
12
+ private buffer;
13
+ private frameWaiter;
14
+ private frameReject;
15
+ private tunWritableWaiter;
16
+ private loopPromise;
17
+ private fwdFrames;
18
+ private deviceIngressPaused;
19
+ private deviceConn;
20
+ private pendingDeviceChunks;
21
+ private deviceDrainScheduled;
22
+ constructor(onFrameWritten?: DeviceToTunProgressHook | undefined);
23
+ start(deviceConn: Socket, tun: TunTap): void;
24
+ notifyTunWritable(): void;
25
+ stop(): Promise<void>;
26
+ private enqueueDeviceData;
27
+ private onDeviceData;
28
+ private maybeResumeDeviceIngress;
29
+ private tryDeliverFrame;
30
+ private readOneFrame;
31
+ private pauseDeviceIngress;
32
+ private resumeDeviceIngress;
33
+ private waitTunWritable;
34
+ private writeFrameToTun;
35
+ private runLoop;
36
+ }
@@ -0,0 +1,229 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import { appendBuffer } from './buffer-utils.js';
3
+ import { fwdDebug } from './debug-log.js';
4
+ import { IPV6_HEADER_SIZE, IPV6_VERSION, MAX_DEVICE_INGRESS_BUFFER, DEVICE_PUMP_YIELD_EVERY_FRAMES, } from './constants.js';
5
+ /**
6
+ * Device→TUN forwarding: read one exact IPv6 frame from the socket, write to TUN,
7
+ * repeat. Yields only when TUN write blocks (via notifyTunWritable from the TUN→device pump).
8
+ */
9
+ export class DeviceToTunPump {
10
+ onFrameWritten;
11
+ cancelled = false;
12
+ running = false;
13
+ buffer = Buffer.alloc(0);
14
+ frameWaiter = null;
15
+ frameReject = null;
16
+ tunWritableWaiter = null;
17
+ loopPromise = null;
18
+ fwdFrames = 0;
19
+ deviceIngressPaused = false;
20
+ deviceConn = null;
21
+ pendingDeviceChunks = [];
22
+ deviceDrainScheduled = false;
23
+ constructor(onFrameWritten) {
24
+ this.onFrameWritten = onFrameWritten;
25
+ }
26
+ start(deviceConn, tun) {
27
+ if (this.running) {
28
+ return;
29
+ }
30
+ this.running = true;
31
+ this.cancelled = false;
32
+ this.deviceConn = deviceConn;
33
+ deviceConn.on('data', (chunk) => this.enqueueDeviceData(chunk));
34
+ fwdDebug('device-pump-start', {});
35
+ this.loopPromise = this.runLoop(deviceConn, tun);
36
+ }
37
+ notifyTunWritable() {
38
+ const waiter = this.tunWritableWaiter;
39
+ if (waiter) {
40
+ this.tunWritableWaiter = null;
41
+ waiter();
42
+ }
43
+ }
44
+ async stop() {
45
+ this.cancelled = true;
46
+ if (this.frameReject) {
47
+ this.frameReject(new Error(DEVICE_PUMP_CANCELLED));
48
+ }
49
+ if (this.tunWritableWaiter) {
50
+ this.tunWritableWaiter();
51
+ }
52
+ this.frameWaiter = null;
53
+ this.frameReject = null;
54
+ this.tunWritableWaiter = null;
55
+ if (this.loopPromise) {
56
+ await this.loopPromise.catch(() => undefined);
57
+ this.loopPromise = null;
58
+ }
59
+ this.buffer = Buffer.alloc(0);
60
+ this.deviceConn = null;
61
+ this.running = false;
62
+ }
63
+ enqueueDeviceData(chunk) {
64
+ if (this.cancelled || chunk.length === 0) {
65
+ return;
66
+ }
67
+ this.pendingDeviceChunks.push(chunk);
68
+ if (this.deviceDrainScheduled) {
69
+ return;
70
+ }
71
+ this.deviceDrainScheduled = true;
72
+ setImmediate(() => {
73
+ this.deviceDrainScheduled = false;
74
+ const chunks = this.pendingDeviceChunks;
75
+ this.pendingDeviceChunks = [];
76
+ for (const pending of chunks) {
77
+ this.onDeviceData(pending);
78
+ }
79
+ });
80
+ }
81
+ onDeviceData(chunk) {
82
+ if (this.cancelled || chunk.length === 0) {
83
+ return;
84
+ }
85
+ this.buffer = appendBuffer(this.buffer, chunk);
86
+ if (this.deviceConn &&
87
+ this.buffer.length > MAX_DEVICE_INGRESS_BUFFER &&
88
+ !this.deviceIngressPaused) {
89
+ this.pauseDeviceIngress(this.deviceConn, 'max-buffer');
90
+ }
91
+ this.tryDeliverFrame();
92
+ }
93
+ maybeResumeDeviceIngress() {
94
+ if (!this.deviceConn ||
95
+ !this.deviceIngressPaused ||
96
+ this.buffer.length > MAX_DEVICE_INGRESS_BUFFER) {
97
+ return;
98
+ }
99
+ this.resumeDeviceIngress(this.deviceConn);
100
+ }
101
+ tryDeliverFrame() {
102
+ const waiter = this.frameWaiter;
103
+ if (!waiter) {
104
+ return;
105
+ }
106
+ const taken = takeOneIpv6Frame(this.buffer);
107
+ if (taken.kind === 'incomplete' || taken.kind === 'resync') {
108
+ if (taken.kind === 'resync') {
109
+ this.buffer = this.buffer.subarray(1);
110
+ this.tryDeliverFrame();
111
+ }
112
+ return;
113
+ }
114
+ this.buffer = shrinkBuffer(this.buffer, taken.length);
115
+ this.frameWaiter = null;
116
+ this.frameReject = null;
117
+ waiter(taken.packet);
118
+ this.maybeResumeDeviceIngress();
119
+ }
120
+ readOneFrame() {
121
+ if (this.cancelled) {
122
+ return Promise.reject(new Error(DEVICE_PUMP_CANCELLED));
123
+ }
124
+ const taken = takeOneIpv6Frame(this.buffer);
125
+ if (taken.kind === 'frame') {
126
+ this.buffer = shrinkBuffer(this.buffer, taken.length);
127
+ return Promise.resolve(taken.packet);
128
+ }
129
+ if (taken.kind === 'resync') {
130
+ this.buffer = this.buffer.subarray(1);
131
+ return this.readOneFrame();
132
+ }
133
+ return new Promise((resolve, reject) => {
134
+ this.frameWaiter = resolve;
135
+ this.frameReject = reject;
136
+ });
137
+ }
138
+ pauseDeviceIngress(deviceConn, reason) {
139
+ if (this.deviceIngressPaused || deviceConn.destroyed) {
140
+ return;
141
+ }
142
+ this.deviceIngressPaused = true;
143
+ deviceConn.pause();
144
+ fwdDebug('ingress-pause', { reason, buf: this.buffer.length });
145
+ }
146
+ resumeDeviceIngress(deviceConn) {
147
+ if (!this.deviceIngressPaused || deviceConn.destroyed) {
148
+ return;
149
+ }
150
+ this.deviceIngressPaused = false;
151
+ deviceConn.resume();
152
+ fwdDebug('ingress-resume', { buf: this.buffer.length });
153
+ }
154
+ waitTunWritable() {
155
+ if (this.cancelled) {
156
+ return Promise.reject(new Error(DEVICE_PUMP_CANCELLED));
157
+ }
158
+ return new Promise((resolve) => {
159
+ this.tunWritableWaiter = resolve;
160
+ });
161
+ }
162
+ async writeFrameToTun(deviceConn, tun, packet) {
163
+ while (!this.cancelled) {
164
+ const bytesWritten = tun.write(packet);
165
+ if (bytesWritten > 0) {
166
+ this.maybeResumeDeviceIngress();
167
+ return;
168
+ }
169
+ // Block on tun.write() without pausing the TLS stream — keep reading into the
170
+ // reassembly buffer while waiting for TUN→device progress to free utun capacity.
171
+ fwdDebug('tun-write-blocked', { frameLen: packet.length, buf: this.buffer.length });
172
+ await this.waitTunWritable();
173
+ }
174
+ }
175
+ async runLoop(deviceConn, tun) {
176
+ try {
177
+ while (!this.cancelled && !deviceConn.destroyed) {
178
+ const packet = await this.readOneFrame();
179
+ if (this.cancelled) {
180
+ break;
181
+ }
182
+ await this.writeFrameToTun(deviceConn, tun, packet);
183
+ this.fwdFrames += 1;
184
+ if (this.fwdFrames === 1 || this.fwdFrames % 200 === 0) {
185
+ fwdDebug('device-pump-write', { len: packet.length, frames: this.fwdFrames });
186
+ }
187
+ this.onFrameWritten?.();
188
+ if (this.fwdFrames % DEVICE_PUMP_YIELD_EVERY_FRAMES === 0) {
189
+ await new Promise((resolve) => setImmediate(resolve));
190
+ }
191
+ }
192
+ }
193
+ catch (err) {
194
+ if (!isPumpCancelled(err)) {
195
+ throw err;
196
+ }
197
+ }
198
+ fwdDebug('device-pump-stop', { frames: this.fwdFrames });
199
+ }
200
+ }
201
+ const DEVICE_PUMP_CANCELLED = 'device pump cancelled';
202
+ function isPumpCancelled(err) {
203
+ return err instanceof Error && err.message === DEVICE_PUMP_CANCELLED;
204
+ }
205
+ function takeOneIpv6Frame(buffer) {
206
+ if (buffer.length < IPV6_HEADER_SIZE) {
207
+ return { kind: 'incomplete' };
208
+ }
209
+ const version = (buffer[0] >> 4) & 0x0f;
210
+ if (version !== IPV6_VERSION) {
211
+ return { kind: 'resync' };
212
+ }
213
+ const payloadLength = buffer.readUInt16BE(4);
214
+ const length = IPV6_HEADER_SIZE + payloadLength;
215
+ if (buffer.length < length) {
216
+ return { kind: 'incomplete' };
217
+ }
218
+ return {
219
+ kind: 'frame',
220
+ packet: buffer.subarray(0, length),
221
+ length,
222
+ };
223
+ }
224
+ function shrinkBuffer(buffer, consumed) {
225
+ if (consumed >= buffer.length) {
226
+ return Buffer.alloc(0);
227
+ }
228
+ return buffer.subarray(consumed);
229
+ }
@@ -14,7 +14,10 @@ export declare class TunnelManager extends EventEmitter<TunnelManagerEvents> {
14
14
  private readonly packetConsumers;
15
15
  private deviceConn;
16
16
  private cleanupPromise;
17
- private tunReadPausedForBackpressure;
17
+ private tunToDevicePump;
18
+ private deviceToTunPump;
19
+ private deviceIngressPaused;
20
+ private fwdDeviceDataChunks;
18
21
  /**
19
22
  * Register a listener for parsed tunnel packets (in addition to the `data` event).
20
23
  *
@@ -57,12 +60,14 @@ export declare class TunnelManager extends EventEmitter<TunnelManagerEvents> {
57
60
  */
58
61
  stop(): Promise<void>;
59
62
  private hasPacketTap;
63
+ private pauseDeviceIngress;
64
+ private resumeDeviceIngress;
60
65
  private processBuffer;
61
66
  private writeDeviceFrameToTun;
62
67
  private tapL4Packet;
63
68
  private dispatchPacketData;
64
- private startTunReadLoop;
65
- private writeTunPacketToDevice;
69
+ private startDeviceToTunPump;
70
+ private startTunToDevicePump;
66
71
  private _performStop;
67
72
  }
68
73
  /**