Linux-first Tauri plugin to manage network state, Wi-Fi, and VPN through NetworkManager over D-Bus.
- Features
- Architecture
- Requirements
- Installation
- Permissions
- Quick Start
- API Reference
- TypeScript Usage
- Event System
- Error Handling
- Caching
- Types
- Package Exports
- Testing & Build
- Contributing
- Scan & list nearby access points with signal strength and security detection
- Connect to open / WEP / WPA-PSK / WPA2-PSK / WPA3-PSK / WPA-EAP networks
- Disconnect from current Wi-Fi
- List and delete saved Wi-Fi connections
- Request explicit scans
- List, create, update, delete VPN profiles (OpenVPN, WireGuard, L2TP, PPTP, SSTP, IKEv2, FortiSSL, OpenConnect, Generic)
- Connect / disconnect VPN by UUID
- Read current VPN status, gateway, IP configuration
- Read current active network (Ethernet / Wi-Fi) — SSID, IP, MAC, signal, security
- Enable / disable wireless and global networking
- Check wireless hardware availability
- Bandwidth stats (download/upload speed, total bytes, uptime)
- Real-time events via Tauri's event system:
network-changed— network state transitionvpn-changed,vpn-connected,vpn-disconnected,vpn-failed
flowchart TB
subgraph Frontend["Tauri Frontend (TypeScript)"]
TS["guest-js/index.ts<br/>invoke() / listen()"]
end
subgraph IPC["Tauri Bridge"]
IPC_BRIDGE["Plugin IPC"]
end
subgraph Rust["Rust Plugin (tauri-plugin-network-manager)"]
direction TB
CMDS["commands.rs<br/>19 #[tauri::command]"]
DESKTOP["desktop.rs<br/>D-Bus logic"]
HELPERS["nm_helpers.rs<br/>security detection"]
STATS["network_stats.rs<br/>bandwidth tracker"]
LIB["lib.rs<br/>state mgmt · caching<br/>event emitter"]
ERR["error.rs<br/>typed errors"]
MODELS["models.rs<br/>data structures"]
CMDS --> DESKTOP
DESKTOP --> HELPERS
DESKTOP --> STATS
LIB --> CMDS
DESKTOP --> ERR
DESKTOP --> MODELS
end
subgraph NM["NetworkManager (D-Bus System Bus)"]
NM_MAIN["org.freedesktop.NetworkManager<br/>AddAndActivateConnection<br/>ActivateConnection<br/>DeactivateConnection"]
NM_SETTINGS["org.freedesktop.NetworkManager.Settings<br/>AddConnection · Update · Delete<br/>ListConnections"]
NM_DEVICE["org.freedesktop.NetworkManager.Device<br/>DeviceType · HwAddress · Ip4Config"]
NM_WIFI["org.freedesktop.NetworkManager.Device.Wireless<br/>AccessPoints · ActiveAccessPoint<br/>RequestScan"]
NM_AP["org.freedesktop.NetworkManager.AccessPoint<br/>Ssid · Strength · Flags<br/>WpaFlags · RsnFlags · KeyMgmt"]
NM_ACTIVE["org.freedesktop.NetworkManager.Connection.Active<br/>State · Type · Devices · Uuid"]
end
Frontend <--> IPC
IPC <--> Rust
DESKTOP -- "zbus 4 (blocking)" --> NM_MAIN
DESKTOP --> NM_SETTINGS
DESKTOP --> NM_DEVICE
DESKTOP --> NM_WIFI
DESKTOP --> NM_AP
DESKTOP --> NM_ACTIVE
| Module | Responsibility |
|---|---|
lib.rs |
Tauri plugin entry point, state management (Arc<RwLock>), network change event emitter with 250 ms debounce |
desktop.rs |
All D-Bus calls via zbus 4: network state, Wi-Fi scan/connect, VPN CRUD, signal listening |
commands.rs |
19 #[tauri::command] functions bridging IPC to desktop.rs |
nm_helpers.rs |
Security type detection (KeyMgmt / WpaFlags / RsnFlags), SSID byte→string conversion, connectivity check |
network_stats.rs |
Bandwidth tracker reading /sys/class/net/<iface>/statistics |
error.rs |
Typed error enum with thiserror, D-Bus error mapping |
models.rs |
All data structures: NetworkInfo, WiFiConnectionConfig, VpnProfile, etc. |
- Linux with NetworkManager running on D-Bus system bus
- Tauri 2 project
- Rust 1.77.2+
- System D-Bus libraries (
libdbus-1-devor equivalent)
[dependencies]
tauri-plugin-network-manager = { git = "https://github.com/Vasak-OS/tauri-plugin-network-manager" }bun add @vasakgroup/plugin-network-manager
# or
npm install @vasakgroup/plugin-network-manager
# or
pnpm add @vasakgroup/plugin-network-manager// src-tauri/src/lib.rs
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_network_manager::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}By default the plugin allows read + Wi-Fi connect/disconnect operations. VPN mutations are opt-in.
The default set includes:
get-network-state,list-wifi-networks,rescan-wificonnect-to-wifi,disconnect-from-wifiget-saved-wifi-networks,delete-wifi-connectiontoggle-network-state,get-wireless-enabled,set-wireless-enabledis-wireless-available,list-vpn-profiles,get-vpn-status
{
"permissions": [
"network-manager:vpn_management",
// or individually:
"network-manager:allow-connect-vpn",
"network-manager:allow-disconnect-vpn",
"network-manager:allow-create-vpn-profile",
"network-manager:allow-update-vpn-profile",
"network-manager:allow-delete-vpn-profile"
]
}import {
listWifiNetworks,
connectToWifi,
disconnectFromWifi,
WiFiSecurityType,
} from '@vasakgroup/plugin-network-manager';
// 1. Scan available networks
const networks = await listWifiNetworks();
console.log(networks.map(n => `${n.ssid} (${n.signal_strength}%)`));
// 2. Connect to a WPA2-PSK network
await connectToWifi({
ssid: 'MyNetwork',
password: 'supersecret',
securityType: WiFiSecurityType.WPA2_PSK,
});
// 3. Disconnect
await disconnectFromWifi();import {
createVpnProfile,
connectVpn,
getVpnStatus,
disconnectVpn,
deleteVpnProfile,
} from '@vasakgroup/plugin-network-manager';
import { listen } from '@tauri-apps/api/event';
import type { VpnEventPayload } from '@vasakgroup/plugin-network-manager';
// Listen for VPN state changes
const unlisten = await listen<VpnEventPayload>('vpn-changed', (e) => {
console.log('VPN state:', e.payload.status.state);
});
// Create and connect
const profile = await createVpnProfile({
id: 'Office VPN',
vpn_type: 'wire-guard',
settings: { endpoint: 'vpn.example.com:51820' },
secrets: { private_key: '***' },
});
await connectVpn(profile.uuid);
console.log(await getVpnStatus());
// Cleanup
await disconnectVpn(profile.uuid);
await deleteVpnProfile(profile.uuid);
unlisten();import { getNetworkStats } from '@vasakgroup/plugin-network-manager';
const stats = await getNetworkStats();
console.log({
download: `${stats.download_speed} B/s`,
upload: `${stats.upload_speed} B/s`,
totalDown: `${(stats.total_downloaded / 1e6).toFixed(1)} MB`,
uptime: `${stats.connection_duration}s`,
});Returns the currently active network connection info, or a default if disconnected.
Scans and returns all visible access points, deduplicated by SSID, sorted by signal strength.
| Option | Type | Default | Description |
|---|---|---|---|
forceRefresh |
boolean |
false |
Bypass in-memory cache |
ttlMs |
number |
3000 |
Cache TTL in milliseconds (250–30000) |
Triggers a RequestScan D-Bus call on all wireless devices and returns fresh results.
Accepts both camelCase (frontend-friendly) and snake_case (Rust wire) formats.
| Field | Type | Required | Description |
|---|---|---|---|
ssid |
string |
yes | Network SSID |
password |
string |
no | PSK / password |
securityType / security_type |
WiFiSecurityType |
yes | See WiFiSecurityType |
username |
string |
no | WPA-EAP identity |
Supported security types:
| Value | D-Bus key-mgmt |
Notes |
|---|---|---|
None |
— | Open network |
Wep |
none |
Static WEP with wep-key0 |
WpaPsk |
wpa-psk |
WPA1-PSK |
Wpa2Psk |
wpa-psk + proto=["rsn"] |
WPA2-PSK |
Wpa3Psk |
sae |
WPA3-SAE |
WpaEap |
wpa-eap |
Enterprise WPA-EAP |
Deactivates the active Wi-Fi connection.
Returns saved Wi-Fi profiles (known networks) from NM settings, with SSID and detected security type.
Deletes the first saved connection matching the given SSID.
Enables or disables all networking. Returns the new state.
Returns whether Wi-Fi radio is enabled.
Enables or disables Wi-Fi radio.
Returns true if at least one Wi-Fi adapter is present.
Returns all VPN profiles sorted by ID.
Returns the current active VPN state, profile info, gateway, and IP.
Activates a VPN by UUID. Throws VpnAlreadyConnected if already connected to the same profile.
Deactivates a VPN. If uuid is omitted, deactivates the currently active VPN (VpnNotActive if none).
Creates a new VPN profile in NM settings. Requires vpn_management permission.
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
yes | Profile display name |
vpn_type |
VpnType |
yes | See VpnType |
autoconnect |
boolean |
no | Auto-connect on boot |
username |
string |
no | VPN username |
gateway |
string |
no | Remote gateway address |
ca_cert_path |
string |
no | CA certificate path |
user_cert_path |
string |
no | User certificate path |
private_key_path |
string |
no | Private key path |
private_key_password |
string |
no | Private key passphrase |
settings |
Record<string, string> |
no | Custom VPN settings |
secrets |
Record<string, string> |
no | Custom VPN secrets (e.g. psk, private_key) |
Updates an existing VPN profile. All fields except uuid are optional.
| Field | Type | Description |
|---|---|---|
uuid |
string |
Profile UUID to update (required) |
id |
string |
New display name |
autoconnect |
boolean |
New auto-connect preference |
username |
string |
New username |
gateway |
string |
New remote address |
ca_cert_path |
string |
New CA cert path |
user_cert_path |
string |
New user cert path |
private_key_path |
string |
New private key path |
private_key_password |
string |
New passphrase |
settings |
Record<string, string> |
Merged into existing settings |
secrets |
Record<string, string> |
Merged into existing secrets |
Deletes a VPN profile by UUID.
Returns bandwidth statistics for the active interface.
interface NetworkStats {
download_speed: number; // bytes/sec
upload_speed: number; // bytes/sec
total_downloaded: number; // bytes
total_uploaded: number; // bytes
connection_duration: number; // seconds
interface: string; // e.g. "wlan0"
}Returns all non-loopback network interface names.
The plugin emits Tauri events when network state changes, with a 250 ms debounce window to coalesce rapid transitions.
| Event | Payload | When |
|---|---|---|
network-changed |
NetworkInfo |
Any network state transition |
vpn-changed |
VpnEventPayload |
VPN state transition |
vpn-connected |
VpnEventPayload |
Transition to Connected |
vpn-disconnected |
VpnEventPayload |
Transition to Disconnected |
vpn-failed |
VpnEventPayload |
Transition to Failed |
import { listen } from '@tauri-apps/api/event';
import type { NetworkInfo, VpnEventPayload } from '@vasakgroup/plugin-network-manager';
// Network changes
const unlistenNet = await listen<NetworkInfo>('network-changed', (event) => {
const n = event.payload;
const status = n.is_connected ? `connected to ${n.ssid}` : 'disconnected';
console.log(`[${n.connection_type}] ${status} (${n.ip_address})`);
});
// VPN failures
const unlistenFail = await listen<VpnEventPayload>('vpn-failed', (event) => {
console.error(`VPN failed: ${event.payload.reason}`);
});
// Cleanup when leaving component
// unlistenNet();
// unlistenFail();All commands throw a typed NetworkManagerError with a code property:
import { connectToWifi, NetworkManagerErrorCode } from '@vasakgroup/plugin-network-manager';
try {
await connectToWifi({ ssid: '...', password: '...', securityType: 'wpa2-psk' });
} catch (e) {
if (e.code === NetworkManagerErrorCode.CONNECTION_FAILED) {
showToast('Connection failed. Check password.');
} else if (e.code === NetworkManagerErrorCode.UNSUPPORTED_SECURITY) {
showToast('This security type is not supported.');
} else {
showToast(`Unexpected error: ${e.message}`);
}
}| Code | Meaning |
|---|---|
NOT_INITIALIZED |
Plugin not initialized |
NO_CONNECTION |
No active network |
PERMISSION_DENIED |
Capability not granted |
UNSUPPORTED_SECURITY |
Security type not implemented |
CONNECTION_FAILED |
D-Bus connection call failed |
NETWORK_NOT_FOUND |
SSID not in saved networks |
OPERATION_FAILED |
Generic failure |
VPN_PROFILE_NOT_FOUND |
UUID not in settings |
VPN_ALREADY_CONNECTED |
Already connected to that profile |
VPN_AUTH_FAILED |
Secret/authentication error |
VPN_INVALID_CONFIG |
Bad profile parameters |
VPN_ACTIVATION_FAILED |
ActivateConnection D-Bus error |
VPN_PLUGIN_UNAVAILABLE |
NM plugin not found |
VPN_NOT_ACTIVE |
No active VPN to disconnect |
UNKNOWN |
Fallback |
Wi-Fi scan results are cached in-memory for 3 seconds by default to avoid redundant D-Bus calls.
// Bypass cache
const fresh = await listWifiNetworks({ forceRefresh: true });
// Custom TTL (500 ms)
const fast = await listWifiNetworks({ ttlMs: 500 });
// rescanWifi always invalidates cache and returns fresh datatype WiFiSecurityType =
| 'none'
| 'wep'
| 'wpa-psk'
| 'wpa-eap'
| 'wpa2-psk'
| 'wpa3-psk';type VpnType =
| 'open-vpn'
| 'wire-guard'
| 'l2tp'
| 'pptp'
| 'sstp'
| 'ikev2'
| 'fortisslvpn'
| 'open-connect'
| 'generic';type VpnConnectionState =
| 'disconnected'
| 'connecting'
| 'connected'
| 'disconnecting'
| 'failed'
| 'unknown';interface NetworkInfo {
name: string;
ssid: string;
connection_type: string; // "wifi" | "Ethernet" | "Unknown"
icon: string; // icon name for the UI
ip_address: string; // "0.0.0.0" when disconnected
mac_address: string;
signal_strength: number; // 0–100
security_type: WiFiSecurityType;
is_connected: boolean;
}interface VpnProfile {
id: string;
uuid: string;
vpn_type: VpnType;
interface_name: string | null;
autoconnect: boolean;
editable: boolean;
last_error: string | null;
}interface VpnStatus {
state: VpnConnectionState;
active_profile_id: string | null;
active_profile_uuid: string | null;
active_profile_name: string | null;
ip_address: string | null;
gateway: string | null;
since_unix_ms: number | null;
}interface VpnEventPayload {
status: VpnStatus;
profile: VpnProfile | null;
reason: string | null; // populated on 'vpn-failed'
}The NPM package provides ESM, CJS, and TypeScript declarations:
{
"main": "./dist-js/index.cjs",
"module": "./dist-js/index.js",
"types": "./dist-js/index.d.ts",
"exports": {
"types": "./dist-js/index.d.ts",
"import": "./dist-js/index.js",
"require": "./dist-js/index.cjs"
}
}# Build the Rust crate
cargo build
# Run Rust tests
cargo test
# Build NPM guest package
bun run build
# Run smoke tests (requires Tauri project)
bun run test- Fork the repository
- Create a feature branch (
git checkout -b feat/my-feature) - Commit your changes (
git commit -am 'feat: add ...') - Push (
git push origin feat/my-feature) - Open a Pull Request
- Use
RUST_LOG=debugorRUST_LOG=traceto enable D-Bus call logging - Monitor NM D-Bus traffic:
busctl monitor org.freedesktop.NetworkManager - Test Wi-Fi connection flow:
nmcli dev wifi listthennmcli connection add ...
GPL-3.0-or-later — see LICENSE.