appium-ios-tuntap 0.3.0 → 0.4.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 +12 -0
- package/README.md +20 -3
- package/binding.gyp +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/platform/create-platform.js +3 -0
- package/lib/platform/require-admin.d.ts +17 -0
- package/lib/platform/require-admin.js +32 -0
- package/lib/platform/windows.d.ts +13 -0
- package/lib/platform/windows.js +195 -0
- package/lib/tunnel/constants.d.ts +10 -0
- package/lib/tunnel/constants.js +10 -0
- package/lib/tunnel/index.d.ts +2 -0
- package/lib/tunnel/index.js +1 -0
- package/lib/{tunnel.d.ts → tunnel/manager.d.ts} +9 -59
- package/lib/{tunnel.js → tunnel/manager.js} +231 -226
- package/lib/tunnel/types.d.ts +71 -0
- package/lib/tunnel/types.js +1 -0
- package/package.json +8 -4
- package/prebuilds/darwin-arm64/appium-ios-tuntap.node +0 -0
- package/prebuilds/darwin-x64/appium-ios-tuntap.node +0 -0
- package/prebuilds/linux-arm64/appium-ios-tuntap.node +0 -0
- package/prebuilds/linux-x64/appium-ios-tuntap.node +0 -0
- package/prebuilds/win32-arm64/appium-ios-tuntap.node +0 -0
- package/prebuilds/win32-x64/appium-ios-tuntap.node +0 -0
- package/scripts/fetch-wintun.mjs +70 -0
- package/src/native/posix_uv_poll_loop.cc +21 -18
- package/src/native/tun_backend_darwin.cc +6 -4
- package/src/native/wintun_loader.cc +26 -0
- package/src/tuntap.cc +9 -2
- package/vendor/wintun/LICENSE.txt +84 -0
- package/vendor/wintun/README.md +37 -0
- package/vendor/wintun/bin/amd64/wintun.dll +0 -0
- package/vendor/wintun/bin/arm/wintun.dll +0 -0
- package/vendor/wintun/bin/arm64/wintun.dll +0 -0
- package/vendor/wintun/bin/x86/wintun.dll +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [0.4.1](https://github.com/appium/appium-ios-tuntap/compare/v0.4.0...v0.4.1) (2026-05-31)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
* Improve tunnel performance ([#45](https://github.com/appium/appium-ios-tuntap/issues/45)) ([576d353](https://github.com/appium/appium-ios-tuntap/commit/576d3535137bb0cf79634ab820b3675db6cbbb86))
|
|
6
|
+
|
|
7
|
+
## [0.4.0](https://github.com/appium/appium-ios-tuntap/compare/v0.3.0...v0.4.0) (2026-05-30)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* implement Windows (WinTun) JavaScript platform layer ([#44](https://github.com/appium/appium-ios-tuntap/issues/44)) ([da2a9bb](https://github.com/appium/appium-ios-tuntap/commit/da2a9bba1d7226f67ce0f00c533df72164aac858))
|
|
12
|
+
|
|
1
13
|
## [0.3.0](https://github.com/appium/appium-ios-tuntap/compare/v0.2.5...v0.3.0) (2026-05-22)
|
|
2
14
|
|
|
3
15
|
### Features
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# TunTap Bridge
|
|
2
2
|
|
|
3
|
-
A native TUN/TAP interface module for Node.js that works on
|
|
3
|
+
A native TUN/TAP interface module for Node.js that works on macOS, Linux, and Windows, with enhanced error handling, signal management, and thread safety.
|
|
4
4
|
|
|
5
5
|
## Description
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ This module provides a Node.js interface to TUN/TAP virtual network devices, all
|
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
- **Cross-platform**: Works on macOS (utun)
|
|
11
|
+
- **Cross-platform**: Works on macOS (utun), Linux (TUN/TAP), and Windows (WinTun)
|
|
12
12
|
- **TypeScript support**: Full TypeScript definitions included
|
|
13
13
|
- **Signal handling**: Graceful shutdown on SIGINT/SIGTERM
|
|
14
14
|
- **Thread safety**: Safe to use from multiple Node.js worker threads
|
|
@@ -88,6 +88,14 @@ On Linux, the module requires:
|
|
|
88
88
|
sudo pacman -S linux-headers
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
+
### Windows
|
|
92
|
+
|
|
93
|
+
On Windows the module uses [WinTun](https://www.wintun.net/) (the same userspace TUN driver shipped with WireGuard). Requirements:
|
|
94
|
+
|
|
95
|
+
1. **`wintun.dll`**: ships with the package. The official signed binaries for `amd64`, `arm64`, `x86`, and `arm` are bundled under `vendor/wintun/bin/<arch>/wintun.dll`; the addon discovers the right one automatically based on its own compile-time architecture. No download or copy step is required.
|
|
96
|
+
2. **Administrator privileges**: required to create the kernel adapter and configure addresses/routes via `netsh`. Launch your shell with **Run as administrator**.
|
|
97
|
+
3. **Build toolchain (only if compiling from source)**: Visual Studio Build Tools 2022 with the C++ workload, the Windows 10 SDK, and Python 3.x on `PATH`.
|
|
98
|
+
|
|
91
99
|
## Usage
|
|
92
100
|
|
|
93
101
|
### Basic Usage
|
|
@@ -207,7 +215,7 @@ socket.connect(port, host, async () => {
|
|
|
207
215
|
|
|
208
216
|
#### Properties
|
|
209
217
|
- `name: string` - The device name (e.g., 'utun0', 'tun0')
|
|
210
|
-
- `fd: number` - The file descriptor
|
|
218
|
+
- `fd: number` - The native file descriptor on POSIX (macOS/Linux). Returns `-1` on Windows; Wintun does not expose a numeric file descriptor.
|
|
211
219
|
|
|
212
220
|
### Error Types
|
|
213
221
|
|
|
@@ -294,3 +302,12 @@ This ensures the signal handler works as intended.
|
|
|
294
302
|
## License
|
|
295
303
|
|
|
296
304
|
Apache-2.0
|
|
305
|
+
|
|
306
|
+
### Third-party software
|
|
307
|
+
|
|
308
|
+
This package redistributes the official signed **WinTun** DLLs (version 0.14.1) from [wintun.net](https://www.wintun.net/) under the bundled-binary license shipped by the WinTun project. The unmodified binaries and the upstream license live under [vendor/wintun/](vendor/wintun/):
|
|
309
|
+
|
|
310
|
+
- `vendor/wintun/bin/{amd64,arm64,x86,arm}/wintun.dll`
|
|
311
|
+
- `vendor/wintun/LICENSE.txt` — the upstream WinTun license; required when redistributing the DLL
|
|
312
|
+
|
|
313
|
+
Maintainers can refresh the bundled binaries with `npm run refresh:wintun` after bumping `WINTUN_VERSION` in [scripts/fetch-wintun.mjs](scripts/fetch-wintun.mjs).
|
package/binding.gyp
CHANGED
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DarwinTunTapPlatform } from './darwin.js';
|
|
2
2
|
import { LinuxTunTapPlatform } from './linux.js';
|
|
3
3
|
import { UnsupportedTunTapPlatform } from './unsupported.js';
|
|
4
|
+
import { WindowsTunTapPlatform } from './windows.js';
|
|
4
5
|
/** @internal Built-in {@link TunTapPlatform} for a Node `process.platform` value. */
|
|
5
6
|
export function createTunTapPlatform(platform) {
|
|
6
7
|
switch (platform) {
|
|
@@ -8,6 +9,8 @@ export function createTunTapPlatform(platform) {
|
|
|
8
9
|
return new DarwinTunTapPlatform();
|
|
9
10
|
case 'linux':
|
|
10
11
|
return new LinuxTunTapPlatform();
|
|
12
|
+
case 'win32':
|
|
13
|
+
return new WindowsTunTapPlatform();
|
|
11
14
|
default:
|
|
12
15
|
return new UnsupportedTunTapPlatform(platform);
|
|
13
16
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throws {@link TunTapPermissionError} unless the current process is an
|
|
3
|
+
* elevated (Administrator) Windows process. Mirrors `assertEffectiveRoot`
|
|
4
|
+
* from {@link ./require-root.ts} for POSIX.
|
|
5
|
+
*/
|
|
6
|
+
export declare function assertAdminOnWindows(): Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* Returns true when the current process is running with Administrator
|
|
9
|
+
* privileges on Windows. Implementation runs `net session` (which always
|
|
10
|
+
* exists, regardless of locale) and inspects the exit code.
|
|
11
|
+
*
|
|
12
|
+
* The result is memoized for the lifetime of the process; admin status cannot
|
|
13
|
+
* change between calls without restarting the shell.
|
|
14
|
+
*/
|
|
15
|
+
export declare const isAdministrator: (() => Promise<boolean>) & {
|
|
16
|
+
cache: Map<unknown, Promise<boolean>>;
|
|
17
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { util } from '@appium/support';
|
|
2
|
+
import { TunTapPermissionError } from '../errors.js';
|
|
3
|
+
import { execFileAsync } from './exec.js';
|
|
4
|
+
/**
|
|
5
|
+
* Throws {@link TunTapPermissionError} unless the current process is an
|
|
6
|
+
* elevated (Administrator) Windows process. Mirrors `assertEffectiveRoot`
|
|
7
|
+
* from {@link ./require-root.ts} for POSIX.
|
|
8
|
+
*/
|
|
9
|
+
export async function assertAdminOnWindows() {
|
|
10
|
+
if (await isAdministrator()) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
throw new TunTapPermissionError('TUN interface configuration and routing require Administrator privileges on Windows. ' +
|
|
14
|
+
'Re-launch the shell with "Run as administrator".');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Returns true when the current process is running with Administrator
|
|
18
|
+
* privileges on Windows. Implementation runs `net session` (which always
|
|
19
|
+
* exists, regardless of locale) and inspects the exit code.
|
|
20
|
+
*
|
|
21
|
+
* The result is memoized for the lifetime of the process; admin status cannot
|
|
22
|
+
* change between calls without restarting the shell.
|
|
23
|
+
*/
|
|
24
|
+
export const isAdministrator = util.memoize(async function isAdministratorUncached() {
|
|
25
|
+
try {
|
|
26
|
+
await execFileAsync('net', ['session'], { windowsHide: true });
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TunTapInterfaceStats, TunTapPlatform } from './types.js';
|
|
2
|
+
/** Windows implementation backed by `netsh` for configuration/routing and
|
|
3
|
+
* PowerShell `Get-NetAdapterStatistics` for byte counters. */
|
|
4
|
+
export declare class WindowsTunTapPlatform implements TunTapPlatform {
|
|
5
|
+
/** @inheritdoc */
|
|
6
|
+
configure(interfaceName: string, address: string, mtu: number): Promise<void>;
|
|
7
|
+
/** @inheritdoc */
|
|
8
|
+
addRoute(interfaceName: string, destination: string): Promise<void>;
|
|
9
|
+
/** @inheritdoc */
|
|
10
|
+
removeRoute(interfaceName: string, destination: string): Promise<void>;
|
|
11
|
+
/** @inheritdoc */
|
|
12
|
+
getStats(interfaceName: string): Promise<TunTapInterfaceStats>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { TunTapError } from '../errors.js';
|
|
2
|
+
import { log } from '../logger.js';
|
|
3
|
+
import { assertAdminOnWindows } from './require-admin.js';
|
|
4
|
+
import { execFileAsync } from './exec.js';
|
|
5
|
+
/** Tightly-restricted character set for adapter names passed into PowerShell. */
|
|
6
|
+
const SAFE_NAME_RE = /^[A-Za-z0-9_\- ]+$/;
|
|
7
|
+
/** Phrases that indicate `netsh` could not find the requested route/address. */
|
|
8
|
+
const MISSING_TARGET_HINTS = [
|
|
9
|
+
'element not found',
|
|
10
|
+
'cannot find',
|
|
11
|
+
'no matching',
|
|
12
|
+
'does not exist',
|
|
13
|
+
'not found',
|
|
14
|
+
];
|
|
15
|
+
/** Windows implementation backed by `netsh` for configuration/routing and
|
|
16
|
+
* PowerShell `Get-NetAdapterStatistics` for byte counters. */
|
|
17
|
+
export class WindowsTunTapPlatform {
|
|
18
|
+
/** @inheritdoc */
|
|
19
|
+
async configure(interfaceName, address, mtu) {
|
|
20
|
+
await assertAdminOnWindows();
|
|
21
|
+
assertSafeAdapterName(interfaceName);
|
|
22
|
+
log.debug(`[win] configure: interface=${interfaceName} address=${address} mtu=${mtu}`);
|
|
23
|
+
await addIpv6Address(interfaceName, address);
|
|
24
|
+
await setIpv6Mtu(interfaceName, mtu);
|
|
25
|
+
}
|
|
26
|
+
/** @inheritdoc */
|
|
27
|
+
async addRoute(interfaceName, destination) {
|
|
28
|
+
await assertAdminOnWindows();
|
|
29
|
+
assertSafeAdapterName(interfaceName);
|
|
30
|
+
log.debug(`[win] addRoute: interface=${interfaceName} destination=${destination}`);
|
|
31
|
+
await addIpv6Route(interfaceName, destination);
|
|
32
|
+
// WinTun presents as an Ethernet adapter, so Windows requires Neighbor
|
|
33
|
+
// Discovery (NDP) before it will send packets through the interface.
|
|
34
|
+
// For /128 host routes we seed a static neighbor entry so NDP is bypassed
|
|
35
|
+
// and the first connection attempt is not silently dropped.
|
|
36
|
+
if (destination.endsWith('/128')) {
|
|
37
|
+
const address = destination.slice(0, -4);
|
|
38
|
+
await addStaticNeighbor(interfaceName, address);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** @inheritdoc */
|
|
42
|
+
async removeRoute(interfaceName, destination) {
|
|
43
|
+
await assertAdminOnWindows();
|
|
44
|
+
assertSafeAdapterName(interfaceName);
|
|
45
|
+
await deleteIpv6Route(interfaceName, destination);
|
|
46
|
+
}
|
|
47
|
+
/** @inheritdoc */
|
|
48
|
+
async getStats(interfaceName) {
|
|
49
|
+
assertSafeAdapterName(interfaceName);
|
|
50
|
+
const script = `Get-NetAdapterStatistics -Name '${interfaceName}' ` +
|
|
51
|
+
'| Select-Object ReceivedBytes,SentBytes,ReceivedUnicastPackets,' +
|
|
52
|
+
'SentUnicastPackets,ReceivedDiscardedPackets,OutboundDiscardedPackets ' +
|
|
53
|
+
'| ConvertTo-Json -Compress';
|
|
54
|
+
const { stdout } = await execFileAsync('powershell', [
|
|
55
|
+
'-NoProfile',
|
|
56
|
+
'-NonInteractive',
|
|
57
|
+
'-ExecutionPolicy',
|
|
58
|
+
'Bypass',
|
|
59
|
+
'-Command',
|
|
60
|
+
script,
|
|
61
|
+
]);
|
|
62
|
+
let parsed;
|
|
63
|
+
try {
|
|
64
|
+
parsed = JSON.parse(stdout);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
throw new TunTapError(`Failed to parse Get-NetAdapterStatistics output: ${stdout.trim()}`);
|
|
68
|
+
}
|
|
69
|
+
const num = (key) => {
|
|
70
|
+
const value = parsed[key];
|
|
71
|
+
const n = typeof value === 'number' ? value : parseInt(String(value ?? ''), 10);
|
|
72
|
+
return Number.isFinite(n) ? n : 0;
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
rxBytes: num('ReceivedBytes'),
|
|
76
|
+
rxPackets: num('ReceivedUnicastPackets'),
|
|
77
|
+
rxErrors: num('ReceivedDiscardedPackets'),
|
|
78
|
+
txBytes: num('SentBytes'),
|
|
79
|
+
txPackets: num('SentUnicastPackets'),
|
|
80
|
+
txErrors: num('OutboundDiscardedPackets'),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Validates an adapter name before embedding in a PowerShell expression. */
|
|
85
|
+
function assertSafeAdapterName(interfaceName) {
|
|
86
|
+
if (!SAFE_NAME_RE.test(interfaceName)) {
|
|
87
|
+
throw new TunTapError(`Refusing to use adapter name with unsupported characters: ${JSON.stringify(interfaceName)}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function isMissingTargetError(err) {
|
|
91
|
+
const message = String(err?.message ?? '').toLowerCase();
|
|
92
|
+
return MISSING_TARGET_HINTS.some((hint) => message.includes(hint));
|
|
93
|
+
}
|
|
94
|
+
async function addIpv6Address(interfaceName, address) {
|
|
95
|
+
try {
|
|
96
|
+
const r = await execFileAsync('netsh', [
|
|
97
|
+
'interface',
|
|
98
|
+
'ipv6',
|
|
99
|
+
'add',
|
|
100
|
+
'address',
|
|
101
|
+
`interface=${interfaceName}`,
|
|
102
|
+
`address=${address}/64`,
|
|
103
|
+
'store=active',
|
|
104
|
+
]);
|
|
105
|
+
log.debug(`[win] add address ok: ${r.stdout.trim() || '(no output)'}`);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
const message = err.message ?? '';
|
|
109
|
+
log.warn(`[win] add address err: ${message}`);
|
|
110
|
+
if (!/already exists|object already/i.test(message)) {
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
log.warn(`Address ${address} may already be configured on ${interfaceName}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function setIpv6Mtu(interfaceName, mtu) {
|
|
117
|
+
try {
|
|
118
|
+
const r = await execFileAsync('netsh', [
|
|
119
|
+
'interface',
|
|
120
|
+
'ipv6',
|
|
121
|
+
'set',
|
|
122
|
+
'subinterface',
|
|
123
|
+
interfaceName,
|
|
124
|
+
`mtu=${mtu}`,
|
|
125
|
+
'store=active',
|
|
126
|
+
]);
|
|
127
|
+
log.debug(`[win] set mtu ok: ${r.stdout.trim() || '(no output)'}`);
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
log.warn(`[win] set mtu err: ${err.message ?? err}`);
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function addIpv6Route(interfaceName, destination) {
|
|
135
|
+
try {
|
|
136
|
+
const r = await execFileAsync('netsh', [
|
|
137
|
+
'interface',
|
|
138
|
+
'ipv6',
|
|
139
|
+
'add',
|
|
140
|
+
'route',
|
|
141
|
+
destination,
|
|
142
|
+
interfaceName,
|
|
143
|
+
'store=active',
|
|
144
|
+
]);
|
|
145
|
+
log.debug(`[win] add route ok: ${r.stdout.trim() || '(no output)'}`);
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
const message = err.message ?? '';
|
|
149
|
+
log.warn(`[win] add route err: ${message}`);
|
|
150
|
+
if (/already exists|object already/i.test(message)) {
|
|
151
|
+
log.debug(`Route to ${destination} already exists`);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
throw err;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function deleteIpv6Route(interfaceName, destination) {
|
|
158
|
+
try {
|
|
159
|
+
await execFileAsync('netsh', [
|
|
160
|
+
'interface',
|
|
161
|
+
'ipv6',
|
|
162
|
+
'delete',
|
|
163
|
+
'route',
|
|
164
|
+
destination,
|
|
165
|
+
interfaceName,
|
|
166
|
+
'store=active',
|
|
167
|
+
]);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
if (isMissingTargetError(err)) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
throw err;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async function addStaticNeighbor(interfaceName, address) {
|
|
177
|
+
log.debug(`[win] addStaticNeighbor: interface=${interfaceName} address=${address}`);
|
|
178
|
+
try {
|
|
179
|
+
const r = await execFileAsync('netsh', [
|
|
180
|
+
'interface',
|
|
181
|
+
'ipv6',
|
|
182
|
+
'add',
|
|
183
|
+
'neighbor',
|
|
184
|
+
interfaceName,
|
|
185
|
+
address,
|
|
186
|
+
'00-00-00-00-00-01',
|
|
187
|
+
'store=active',
|
|
188
|
+
]);
|
|
189
|
+
log.debug(`[win] add neighbor ok: ${r.stdout.trim() || '(no output)'}`);
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
const msg = err.message ?? String(err);
|
|
193
|
+
log.warn(`[win] add neighbor err: ${msg}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** CDTunnel lockdown handshake MTU (IPv6 minimum). */
|
|
2
|
+
export declare const CD_TUNNEL_MTU = 1280;
|
|
3
|
+
export declare const CD_TUNNEL_MAGIC = "CDTunnel";
|
|
4
|
+
export declare const CD_TUNNEL_MAGIC_SIZE = 8;
|
|
5
|
+
export declare const CD_TUNNEL_HEADER_SIZE: number;
|
|
6
|
+
export declare const CD_TUNNEL_HANDSHAKE_TIMEOUT_MS = 30000;
|
|
7
|
+
export declare const IPV6_HEADER_SIZE = 40;
|
|
8
|
+
export declare const IPV6_VERSION = 6;
|
|
9
|
+
export declare const IPPROTO_TCP = 6;
|
|
10
|
+
export declare const IPPROTO_UDP = 17;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** CDTunnel lockdown handshake MTU (IPv6 minimum). */
|
|
2
|
+
export const CD_TUNNEL_MTU = 1280;
|
|
3
|
+
export const CD_TUNNEL_MAGIC = 'CDTunnel';
|
|
4
|
+
export const CD_TUNNEL_MAGIC_SIZE = 8;
|
|
5
|
+
export const CD_TUNNEL_HEADER_SIZE = CD_TUNNEL_MAGIC_SIZE + 2;
|
|
6
|
+
export const CD_TUNNEL_HANDSHAKE_TIMEOUT_MS = 30_000;
|
|
7
|
+
export const IPV6_HEADER_SIZE = 40;
|
|
8
|
+
export const IPV6_VERSION = 6;
|
|
9
|
+
export const IPPROTO_TCP = 6;
|
|
10
|
+
export const IPPROTO_UDP = 17;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TunnelManager, connectToTunnelLockdown, exchangeCoreTunnelParameters } from './manager.js';
|
|
@@ -1,57 +1,7 @@
|
|
|
1
|
-
import { TunTap } from '
|
|
1
|
+
import { TunTap } from '../TunTap.js';
|
|
2
2
|
import { EventEmitter } from 'node:events';
|
|
3
|
-
import { Socket } from 'node:net';
|
|
4
|
-
import {
|
|
5
|
-
export interface PacketData {
|
|
6
|
-
protocol: 'TCP' | 'UDP';
|
|
7
|
-
src: string;
|
|
8
|
-
dst: string;
|
|
9
|
-
sourcePort: number;
|
|
10
|
-
destPort: number;
|
|
11
|
-
payload: Buffer;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Event names and listener argument tuples for {@link TunnelManager}
|
|
15
|
-
* (matches Node’s `EventEmitter` event map shape).
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* tunnelManager.on('data', (packet) => {
|
|
19
|
-
* // `packet` is PacketData
|
|
20
|
-
* });
|
|
21
|
-
*/
|
|
22
|
-
export interface PacketConsumer {
|
|
23
|
-
/**
|
|
24
|
-
* Invoked for each parsed TCP/UDP payload extracted from the tunnel stream.
|
|
25
|
-
*
|
|
26
|
-
* @param packet — decoded addresses, ports, and payload
|
|
27
|
-
*/
|
|
28
|
-
onPacket(packet: PacketData): void;
|
|
29
|
-
}
|
|
30
|
-
export interface TunnelManagerEvents {
|
|
31
|
-
data: [packet: PacketData];
|
|
32
|
-
}
|
|
33
|
-
export interface TunnelConnection {
|
|
34
|
-
Address: string;
|
|
35
|
-
RsdPort?: number;
|
|
36
|
-
tunnelManager: TunnelManager;
|
|
37
|
-
/** Tear down the tunnel, close the TUN device, and end the socket when appropriate. */
|
|
38
|
-
closer: () => Promise<void>;
|
|
39
|
-
/** @param consumer — receives packets for the lifetime of the registration */
|
|
40
|
-
addPacketConsumer(consumer: PacketConsumer): void;
|
|
41
|
-
/** @param consumer — must be the same reference passed to {@link TunnelConnection.addPacketConsumer} */
|
|
42
|
-
removePacketConsumer(consumer: PacketConsumer): void;
|
|
43
|
-
/** @returns async iterator of packets until the tunnel is stopped */
|
|
44
|
-
getPacketStream(): AsyncIterable<PacketData>;
|
|
45
|
-
}
|
|
46
|
-
interface TunnelClientParameters {
|
|
47
|
-
address: string;
|
|
48
|
-
mtu: number;
|
|
49
|
-
}
|
|
50
|
-
interface TunnelInfo {
|
|
51
|
-
clientParameters: TunnelClientParameters;
|
|
52
|
-
serverAddress: string;
|
|
53
|
-
serverRSDPort?: number;
|
|
54
|
-
}
|
|
3
|
+
import type { Socket } from 'node:net';
|
|
4
|
+
import type { PacketConsumer, PacketData, TunnelConnection, TunnelInfo, TunnelManagerEvents } from './types.js';
|
|
55
5
|
/**
|
|
56
6
|
* Bridges a CoreDevice tunnel `Socket` and a {@link TunTap} interface: IPv6 framing, TUN I/O, and packet fan-out.
|
|
57
7
|
* Emits {@link TunnelManagerEvents} (currently `data` with {@link PacketData}) for TCP/UDP packets, same as registered consumers.
|
|
@@ -59,14 +9,11 @@ interface TunnelInfo {
|
|
|
59
9
|
export declare class TunnelManager extends EventEmitter<TunnelManagerEvents> {
|
|
60
10
|
private tun;
|
|
61
11
|
private cancelled;
|
|
62
|
-
private
|
|
12
|
+
private mtu;
|
|
63
13
|
private buffer;
|
|
64
|
-
private packetConsumers;
|
|
65
|
-
private packetQueue;
|
|
14
|
+
private readonly packetConsumers;
|
|
66
15
|
private deviceConn;
|
|
67
16
|
private cleanupPromise;
|
|
68
|
-
/** Creates a manager with no TUN device until {@link TunnelManager.setupInterface} succeeds. */
|
|
69
|
-
constructor();
|
|
70
17
|
/**
|
|
71
18
|
* Register a listener for parsed tunnel packets (in addition to the `data` event).
|
|
72
19
|
*
|
|
@@ -108,7 +55,11 @@ export declare class TunnelManager extends EventEmitter<TunnelManagerEvents> {
|
|
|
108
55
|
* @returns the same promise if already stopping/stopped
|
|
109
56
|
*/
|
|
110
57
|
stop(): Promise<void>;
|
|
58
|
+
private hasPacketTap;
|
|
111
59
|
private processBuffer;
|
|
60
|
+
private writeDeviceFrameToTun;
|
|
61
|
+
private tapL4Packet;
|
|
62
|
+
private dispatchPacketData;
|
|
112
63
|
private startTunReadLoop;
|
|
113
64
|
private _performStop;
|
|
114
65
|
}
|
|
@@ -126,4 +77,3 @@ export declare function exchangeCoreTunnelParameters(socket: Socket): Promise<Tu
|
|
|
126
77
|
* @returns connection handle with {@link TunnelConnection.closer} and packet APIs
|
|
127
78
|
*/
|
|
128
79
|
export declare function connectToTunnelLockdown(secureServiceSocket: Socket): Promise<TunnelConnection>;
|
|
129
|
-
export {};
|