appium-ios-tuntap 0.4.3 → 0.4.4

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,9 @@
1
+ ## [0.4.4](https://github.com/appium/appium-ios-tuntap/compare/v0.4.3...v0.4.4) (2026-06-12)
2
+
3
+ ### Bug Fixes
4
+
5
+ * Update tunnel backpressure handling ([#50](https://github.com/appium/appium-ios-tuntap/issues/50)) ([28b8bce](https://github.com/appium/appium-ios-tuntap/commit/28b8bce8edcf2a0446a653bbec759e6b4a199827))
6
+
1
7
  ## [0.4.3](https://github.com/appium/appium-ios-tuntap/compare/v0.4.2...v0.4.3) (2026-06-10)
2
8
 
3
9
  ### 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.
@@ -16,3 +16,5 @@ export declare const IPV6_HEADER_SIZE = 40;
16
16
  export declare const IPV6_VERSION = 6;
17
17
  export declare const IPPROTO_TCP = 6;
18
18
  export declare const IPPROTO_UDP = 17;
19
+ /** Pause device ingress when the reassembly buffer exceeds this size. */
20
+ export declare const MAX_DEVICE_INGRESS_BUFFER: number;
@@ -16,3 +16,5 @@ export const IPV6_HEADER_SIZE = 40;
16
16
  export const IPV6_VERSION = 6;
17
17
  export const IPPROTO_TCP = 6;
18
18
  export const IPPROTO_UDP = 17;
19
+ /** Pause device ingress when the reassembly buffer exceeds this size. */
20
+ export const MAX_DEVICE_INGRESS_BUFFER = 8 * 1024 * 1024;
@@ -15,6 +15,7 @@ export declare class TunnelManager extends EventEmitter<TunnelManagerEvents> {
15
15
  private deviceConn;
16
16
  private cleanupPromise;
17
17
  private tunReadPausedForBackpressure;
18
+ private deviceIngressPausedForTun;
18
19
  /**
19
20
  * Register a listener for parsed tunnel packets (in addition to the `data` event).
20
21
  *
@@ -59,6 +60,9 @@ export declare class TunnelManager extends EventEmitter<TunnelManagerEvents> {
59
60
  private hasPacketTap;
60
61
  private processBuffer;
61
62
  private writeDeviceFrameToTun;
63
+ private shouldResumeDeviceIngress;
64
+ private pauseDeviceIngress;
65
+ private resumeDeviceIngress;
62
66
  private tapL4Packet;
63
67
  private dispatchPacketData;
64
68
  private startTunReadLoop;
@@ -2,7 +2,7 @@ import { log } from '../logger.js';
2
2
  import { TunTap } from '../TunTap.js';
3
3
  import { EventEmitter } from 'node:events';
4
4
  import { Buffer } from 'node:buffer';
5
- import { CD_TUNNEL_HANDSHAKE_TIMEOUT_MS, CD_TUNNEL_HEADER_SIZE, CD_TUNNEL_MAGIC, CD_TUNNEL_MAGIC_SIZE, CD_TUNNEL_MTU, DEFAULT_TUN_POLL_QUEUE_DEPTH, FAST_TUN_POLL_QUEUE_DEPTH, IPV6_HEADER_SIZE, LARGE_TUN_POLL_BUFFER, MAX_TUN_POLL_BUFFER, IPV6_VERSION, IPPROTO_TCP, IPPROTO_UDP, } from './constants.js';
5
+ import { CD_TUNNEL_HANDSHAKE_TIMEOUT_MS, CD_TUNNEL_HEADER_SIZE, CD_TUNNEL_MAGIC, CD_TUNNEL_MAGIC_SIZE, CD_TUNNEL_MTU, DEFAULT_TUN_POLL_QUEUE_DEPTH, FAST_TUN_POLL_QUEUE_DEPTH, IPV6_HEADER_SIZE, LARGE_TUN_POLL_BUFFER, MAX_DEVICE_INGRESS_BUFFER, MAX_TUN_POLL_BUFFER, IPV6_VERSION, IPPROTO_TCP, IPPROTO_UDP, } from './constants.js';
6
6
  import { appendBuffer } from './buffer-utils.js';
7
7
  /**
8
8
  * Bridges a CoreDevice tunnel `Socket` and a {@link TunTap} interface: IPv6 framing, TUN I/O, and packet fan-out.
@@ -17,6 +17,7 @@ export class TunnelManager extends EventEmitter {
17
17
  deviceConn = null;
18
18
  cleanupPromise = null;
19
19
  tunReadPausedForBackpressure = false;
20
+ deviceIngressPausedForTun = false;
20
21
  /**
21
22
  * Register a listener for parsed tunnel packets (in addition to the `data` event).
22
23
  *
@@ -136,7 +137,9 @@ export class TunnelManager extends EventEmitter {
136
137
  }
137
138
  try {
138
139
  this.buffer = appendBuffer(this.buffer, data);
139
- // Process IPv6 packets
140
+ if (this.buffer.length > MAX_DEVICE_INGRESS_BUFFER) {
141
+ this.pauseDeviceIngress();
142
+ }
140
143
  this.processBuffer();
141
144
  }
142
145
  catch (err) {
@@ -208,17 +211,40 @@ export class TunnelManager extends EventEmitter {
208
211
  this.buffer = this.buffer.subarray(offset);
209
212
  }
210
213
  }
214
+ if (this.deviceIngressPausedForTun && this.shouldResumeDeviceIngress()) {
215
+ this.resumeDeviceIngress();
216
+ }
211
217
  }
212
218
  writeDeviceFrameToTun(tun, packet, nextHeader) {
213
219
  tun.write(packet);
214
220
  if (!this.hasPacketTap()) {
215
221
  return;
216
222
  }
217
- const bytesWritten = packet.length;
218
223
  const { src, dst } = ipv6Endpoints(packet);
219
- log.debug(`Device → TUN: ${bytesWritten} bytes, IPv6 src=${src}, dst=${dst}`);
224
+ log.debug(`Device → TUN: ${packet.length} bytes, IPv6 src=${src}, dst=${dst}`);
220
225
  this.tapL4Packet(packet, nextHeader, src, dst);
221
226
  }
227
+ shouldResumeDeviceIngress() {
228
+ if (this.buffer.length === 0) {
229
+ return true;
230
+ }
231
+ // All complete frames were written; waiting on more socket bytes to finish the tail frame.
232
+ return nextIpv6Frame(this.buffer, 0).kind === 'incomplete';
233
+ }
234
+ pauseDeviceIngress() {
235
+ if (this.deviceIngressPausedForTun || !this.deviceConn || this.deviceConn.destroyed) {
236
+ return;
237
+ }
238
+ this.deviceIngressPausedForTun = true;
239
+ this.deviceConn.pause();
240
+ }
241
+ resumeDeviceIngress() {
242
+ if (!this.deviceIngressPausedForTun || !this.deviceConn || this.deviceConn.destroyed) {
243
+ return;
244
+ }
245
+ this.deviceIngressPausedForTun = false;
246
+ this.deviceConn.resume();
247
+ }
222
248
  tapL4Packet(packet, nextHeader, src, dst) {
223
249
  let packetData = null;
224
250
  if (nextHeader === IPPROTO_UDP) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium-ios-tuntap",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "Native TUN/TAP interface module for Node.js",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -18,8 +18,8 @@
18
18
  "refresh:wintun": "node scripts/fetch-wintun.mjs",
19
19
  "prepare": "npm run build",
20
20
  "test": "npm run test:integration && npm run test:unit",
21
- "test:unit": "mocha \"test/unit/**/*.spec.mjs\" --exit --timeout 2m",
22
- "test:integration": "mocha \"test/integration/**/*.spec.mjs\" --exit --timeout 2m"
21
+ "test:unit": "node --test --test-force-exit --test-timeout=120000 \"test/unit/**/*.spec.mjs\"",
22
+ "test:integration": "node --test --test-force-exit --test-timeout=120000 \"test/integration/**/*.spec.mjs\""
23
23
  },
24
24
  "files": [
25
25
  "src/tuntap.cc",
@@ -61,7 +61,6 @@
61
61
  "@types/node": "^25.0.1",
62
62
  "commander": "^14.0.3",
63
63
  "conventional-changelog-conventionalcommits": "^9.0.0",
64
- "mocha": "^11.7.5",
65
64
  "prebuildify": "^6.0.1",
66
65
  "prettier": "^3.0.0",
67
66
  "semantic-release": "^25.0.2"
@@ -134,6 +134,9 @@ public:
134
134
 
135
135
  ssize_t bytes_written = write(fd_.get(), write_frame_.data(), write_frame_.size());
136
136
  if (bytes_written < 0) {
137
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
138
+ return 0;
139
+ }
137
140
  error = std::string("Write error: ") + strerror(errno);
138
141
  return -1;
139
142
  }
@@ -104,6 +104,9 @@ public:
104
104
  }
105
105
  ssize_t bytes_written = write(fd_.get(), data, length);
106
106
  if (bytes_written < 0) {
107
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
108
+ return 0;
109
+ }
107
110
  error = std::string("Write error: ") + strerror(errno);
108
111
  return -1;
109
112
  }