Tauri Network Manager Plugin
Linux-first Tauri plugin to manage network state, Wi-Fi, and VPN through NetworkManager over D-Bus.
Table of Contents
- Features
- Architecture
- Requirements
- Installation
- Permissions
- Quick Start
- API Reference
- TypeScript Usage
- Event System
- Error Handling
- Caching
- Types
- Package Exports
- Testing & Build
- Contributing
Features
Wi-Fi
- 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
VPN
- 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
Network State
- Read current active network (Ethernet / Wi-Fi) — SSID, IP, MAC, signal, security
- Enable / disable wireless and global networking
- Check wireless hardware availability
Monitoring
- 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
Architecture
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
Key modules
| 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. |
Requirements
- Linux with NetworkManager running on D-Bus system bus
- Tauri 2 project
- Rust 1.77.2+
- System D-Bus libraries (
libdbus-1-devor equivalent)
Installation
Cargo
[]
= { = "https://github.com/Vasak-OS/tauri-plugin-network-manager" }
NPM (guest bindings)
# or
# or
Register the plugin
// src-tauri/src/lib.rs
Permissions
By default the plugin allows read + Wi-Fi connect/disconnect operations. VPN mutations are opt-in.
Default permission set
// src-tauri/capabilities/default.json
{
"identifier": "default",
"windows": ["main"],
"permissions": [
"network-manager:default"
]
}
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
VPN management permission
{
"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"
]
}
Quick Start
Wi-Fi — scan and connect
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();
VPN — create, connect, and listen
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();
Network monitoring
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`,
});
API Reference
getCurrentNetworkState(): Promise<NetworkInfo>
Returns the currently active network connection info, or a default if disconnected.
listWifiNetworks(options?: ListWifiNetworksOptions): Promise<NetworkInfo[]>
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) |
rescanWifi(): Promise<NetworkInfo[]>
Triggers a RequestScan D-Bus call on all wireless devices and returns fresh results.
connectToWifi(config: ConnectToWifiInput | WiFiConnectionConfig): Promise<void>
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 |
disconnectFromWifi(): Promise<void>
Deactivates the active Wi-Fi connection.
getSavedWifiNetworks(): Promise<NetworkInfo[]>
Returns saved Wi-Fi profiles (known networks) from NM settings, with SSID and detected security type.
deleteWifiConnection(ssid: string): Promise<void>
Deletes the first saved connection matching the given SSID.
toggleNetwork(enabled: boolean): Promise<boolean>
Enables or disables all networking. Returns the new state.
getWirelessEnabled(): Promise<boolean>
Returns whether Wi-Fi radio is enabled.
setWirelessEnabled(enabled: boolean): Promise<void>
Enables or disables Wi-Fi radio.
isWirelessAvailable(): Promise<boolean>
Returns true if at least one Wi-Fi adapter is present.
listVpnProfiles(): Promise<VpnProfile[]>
Returns all VPN profiles sorted by ID.
getVpnStatus(): Promise<VpnStatus>
Returns the current active VPN state, profile info, gateway, and IP.
connectVpn(uuid: string): Promise<void>
Activates a VPN by UUID. Throws VpnAlreadyConnected if already connected to the same profile.
disconnectVpn(uuid?: string): Promise<void>
Deactivates a VPN. If uuid is omitted, deactivates the currently active VPN (VpnNotActive if none).
createVpnProfile(config: VpnCreateInput): Promise<VpnProfile>
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) |
updateVpnProfile(config: VpnUpdateInput): Promise<VpnProfile>
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 |
deleteVpnProfile(uuid: string): Promise<void>
Deletes a VPN profile by UUID.
getNetworkStats(): Promise<NetworkStats>
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"
}
getNetworkInterfaces(): Promise<string[]>
Returns all non-loopback network interface names.
Event System
The plugin emits Tauri events when network state changes, with a 250 ms debounce window to coalesce rapid transitions.
Events
| 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 |
Example
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();
Error Handling
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}`);
}
}
Error codes
| 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 |
Caching
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 data
Types
WiFiSecurityType
type WiFiSecurityType =
| 'none'
| 'wep'
| 'wpa-psk'
| 'wpa-eap'
| 'wpa2-psk'
| 'wpa3-psk';
VpnType
type VpnType =
| 'open-vpn'
| 'wire-guard'
| 'l2tp'
| 'pptp'
| 'sstp'
| 'ikev2'
| 'fortisslvpn'
| 'open-connect'
| 'generic';
VpnConnectionState
type VpnConnectionState =
| 'disconnected'
| 'connecting'
| 'connected'
| 'disconnecting'
| 'failed'
| 'unknown';
NetworkInfo
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;
}
VpnProfile
interface VpnProfile {
id: string;
uuid: string;
vpn_type: VpnType;
interface_name: string | null;
autoconnect: boolean;
editable: boolean;
last_error: string | null;
}
VpnStatus
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;
}
VpnEventPayload
interface VpnEventPayload {
status: VpnStatus;
profile: VpnProfile | null;
reason: string | null; // populated on 'vpn-failed'
}
Package Exports
The NPM package provides ESM, CJS, and TypeScript declarations:
Testing & Build
# Build the Rust crate
# Run Rust tests
# Build NPM guest package
# Run smoke tests (requires Tauri project)
Contributing
- 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
Development tips
- 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 ...
License
GPL-3.0-or-later — see LICENSE.