A native TUN/TAP interface module for Node.js that works on both macOS and Linux, with enhanced error handling, signal management, and thread safety.
This module provides a Node.js interface to TUN/TAP virtual network devices, allowing you to create and manage network tunnels from JavaScript/TypeScript. It's useful for VPNs, network tunneling, and other network-related applications.
- Cross-platform: Works on macOS (utun) and Linux (TUN/TAP)
- TypeScript support: Full TypeScript definitions included
- Signal handling: Graceful shutdown on SIGINT/SIGTERM
- Thread safety: Safe to use from multiple Node.js worker threads
- Resource management: Automatic cleanup of file descriptors and network interfaces
- Enhanced error handling: Custom error types for better debugging
- Input validation: Validates IPv6 addresses, MTU ranges, and buffer sizes
- Performance optimized: Built with C++17 and compiler optimizations
- Network statistics: Get interface statistics (RX/TX bytes, packets, errors)
npm install appium-ios-tuntap
On macOS, the module uses the built-in utun interfaces. No additional setup is required, but you'll need administrator privileges to create and configure the interfaces.
On Linux, the module requires:
-
TUN/TAP Kernel Module: The TUN/TAP kernel module must be loaded.
# Check if the module is loaded lsmod | grep tun # If not loaded, load it sudo modprobe tun # To load it automatically at boot echo "tun" | sudo tee -a /etc/modules
-
Permissions: The user running the application needs access to
/dev/net/tun
.# Option 1: Run your application with sudo sudo node your-app.js # Option 2: Add your user to the 'tun' group (if it exists) sudo usermod -a -G tun your-username # Option 3: Create a udev rule to set permissions echo 'KERNEL=="tun", GROUP="your-username", MODE="0660"' | sudo tee /etc/udev/rules.d/99-tuntap.rules sudo udevadm control --reload-rules sudo udevadm trigger
-
iproute2 Package: The
ip
command is required for configuring interfaces.# Debian/Ubuntu sudo apt install iproute2 # CentOS/RHEL sudo yum install iproute # Arch Linux sudo pacman -S iproute2
-
Development Headers: If you're building from source, you'll need the Linux kernel headers.
# Debian/Ubuntu sudo apt install linux-headers-$(uname -r) # CentOS/RHEL sudo yum install kernel-devel # Arch Linux sudo pacman -S linux-headers
import { TunTap } from 'appium-ios-tuntap';
// Create a TUN device
const tun = new TunTap();
// Open the device
if (tun.open()) {
console.log(`Opened TUN device: ${tun.name}`);
// Configure the device with an IPv6 address and MTU
await tun.configure('fd00::1', 1500);
// Add a route
await tun.addRoute('fd00::/64');
// Read from the device
const data = tun.read(4096);
if (data.length > 0) {
console.log(`Read ${data.length} bytes`);
}
// Write to the device
const buffer = Buffer.from([/* your packet data */]);
const bytesWritten = tun.write(buffer);
console.log(`Wrote ${bytesWritten} bytes`);
// Get interface statistics
const stats = await tun.getStats();
console.log('RX bytes:', stats.rxBytes);
console.log('TX bytes:', stats.txBytes);
// Close the device when done
tun.close();
}
import { TunTap, TunTapError, TunTapPermissionError, TunTapDeviceError } from 'appium-ios-tuntap';
try {
const tun = new TunTap();
tun.open();
await tun.configure('fe80::1', 1500);
// ... use the device ...
tun.close();
} catch (err) {
if (err instanceof TunTapPermissionError) {
console.error('Permission denied. Please run with sudo.');
} else if (err instanceof TunTapDeviceError) {
console.error('Device error:', err.message);
} else if (err instanceof TunTapError) {
console.error('TUN/TAP error:', err.message);
} else {
console.error('Unexpected error:', err);
}
}
import { connectToTunnelLockdown } from 'appium-ios-tuntap';
import { Socket } from 'net';
// Create a socket connection to your tunnel endpoint
const socket = new Socket();
socket.connect(port, host, async () => {
try {
// Establish tunnel connection
const tunnel = await connectToTunnelLockdown(socket);
console.log('Tunnel established:', tunnel.Address);
// Add packet consumer
tunnel.addPacketConsumer({
onPacket: (packet) => {
console.log(`${packet.protocol} packet: ${packet.src}:${packet.sourcePort} → ${packet.dst}:${packet.destPort}`);
}
});
// Or use async iteration
for await (const packet of tunnel.getPacketStream()) {
console.log('Received packet:', packet);
}
// Close tunnel when done
await tunnel.closer();
} catch (err) {
console.error('Tunnel error:', err);
}
});
new TunTap(name?: string)
- Create a new TUN/TAP device instance
open(): boolean
- Open the TUN deviceclose(): boolean
- Close the TUN deviceread(maxSize?: number): Buffer
- Read data from the device (default: 4096 bytes)write(data: Buffer): number
- Write data to the deviceconfigure(address: string, mtu?: number): Promise<void>
- Configure IPv6 address and MTUaddRoute(destination: string): Promise<void>
- Add a route to the deviceremoveRoute(destination: string): Promise<void>
- Remove a route from the devicegetStats(): Promise<Stats>
- Get interface statistics
name: string
- The device name (e.g., 'utun0', 'tun0')fd: number
- The file descriptor of the device
TunTapError
- Base error class for all TUN/TAP errorsTunTapPermissionError
- Thrown when there are permission issuesTunTapDeviceError
- Thrown when the device is not available or cannot be opened
The module automatically handles SIGINT and SIGTERM signals for graceful shutdown. All open devices will be closed and network interfaces cleaned up when the process exits.
-
"TUN/TAP device not available": The TUN/TAP kernel module is not loaded.
- Solution:
sudo modprobe tun
- Solution:
-
"Permission denied" when opening /dev/net/tun: The user doesn't have sufficient permissions.
- Solution: Run with sudo or add your user to the 'tun' group.
-
"Permission denied" when configuring the interface: The user doesn't have sudo privileges.
- Solution: Run the application with sudo or configure sudo to allow the specific commands without a password.
-
"Command not found" when configuring the interface: The
ip
command is not available.- Solution: Install the iproute2 package.
-
"Failed to create control socket": The application doesn't have sufficient permissions.
- Solution: Run with sudo.
-
"Could not find an available utun device": All utun devices are in use.
- Solution: Close other applications that might be using utun devices.
Enable debug logging by running your application with the --debug
flag:
node your-app.js --debug
Most tests for this module require root privileges (sudo) to create and manage TUN/TAP devices.
- If you run the tests without root, privileged tests will be automatically skipped.
- Some tests may interact with system networking; use caution on production systems.
- The test suite is designed to clean up after itself, but always verify no stray TUN/TAP devices remain after running.
From the project root, run:
sudo npx mocha test/tuntap-unit.spec.js
Or, to run all tests in the test/
directory:
sudo npx mocha
If you are not running as root, you will see a message that tests are skipped.
Automated tests cannot reliably verify process cleanup on SIGINT/SIGTERM due to test runner limitations.
To manually verify the fix for signal handling (introduced in v0.0.4):
- Run the CLI utility:
sudo node test/test-tuntap.js
- While it is running, press
Ctrl+C
to send SIGINT. - Confirm that:
- The process exits immediately.
- All TUN/TAP devices are closed and cleaned up.
This ensures the signal handler works as intended.
Apache-2.0
upstream/main
Apache-2.0
upstream/main