A Kotlin Multiplatform WireGuard runtime backed by BoringTun and a native Rust daemon.
WG-Kotlin provides a small Kotlin API for opening WireGuard userspace VPN sessions. Crypto and key helpers are backed by BoringTun through UniFFI, while privileged TUN, route, and DNS work is handled by a localhost Rust daemon.
- Current public library target: JVM.
- Real VPN sessions require the Rust daemon running with platform network privileges.
- Maven publishing is configured, but local publishing is the current setup path.
- Interface names intentionally use
utun[0-9]+on every daemon platform.
- Small Kotlin API: Create a
Vpn, pass aVpnConfiguration, then queryinformation()or callstop(). - BoringTun Runtime: WireGuard key generation and peer sessions are backed by Rust BoringTun bindings.
- Native Daemon: TUN packet I/O, route installation, and DNS configuration run in a standalone Rust process.
- gRPC Protocol: Kotlin and Rust share daemon messages through generated protobuf contracts.
- Runtime Snapshots: Interface state includes addresses, DNS, MTU, original configuration, and peer stats.
- Cross-Platform Daemon Adapters: Linux, macOS, and Windows route/DNS adapters live in the Rust daemon.
Publish the Kotlin library to Maven Local:
./gradlew :wg-kotlin:publishToMavenLocalThen add it to your JVM/KMP project:
repositories {
mavenLocal()
mavenCentral()
}
kotlin {
sourceSets {
commonMain.dependencies {
implementation("com.rafambn:wg-kotlin:0.4.5")
}
}
}Build the daemon:
cargo build --manifest-path wg-kotlin-daemon-rust/daemon/Cargo.toml --releaseRun the daemon with the privileges required by your OS network stack:
wg-kotlin-daemon-rust/target/release/wg-kotlin-daemon --host 127.0.0.1 --port 8787The JVM client uses 127.0.0.1:8787 by default. Override it with system properties:
-Dwgkotlin.daemon.host=127.0.0.1
-Dwgkotlin.daemon.port=8787Generate a key pair:
import com.rafambn.wgkotlin.util.WireGuardKeys
val localKeys = WireGuardKeys.generateKeyPair()
val presharedKey = WireGuardKeys.generatePresharedKey()Open a VPN session:
import com.rafambn.wgkotlin.DnsConfig
import com.rafambn.wgkotlin.Vpn
import com.rafambn.wgkotlin.VpnConfiguration
import com.rafambn.wgkotlin.VpnPeer
val vpn = Vpn(interfaceName = "utun0")
vpn.open(
VpnConfiguration(
interfaceName = "utun0",
privateKey = localKeys.privateKey,
addresses = mutableListOf("10.0.0.2/32"),
dns = DnsConfig(
servers = listOf("1.1.1.1"),
searchDomains = listOf("example.com"),
),
peers = listOf(
VpnPeer(
publicKey = "remote-peer-public-key-base64",
presharedKey = presharedKey,
endpointAddress = "203.0.113.10",
endpointPort = 51820,
allowedIps = listOf("0.0.0.0/0"),
persistentKeepalive = 25,
),
),
),
)
val information = vpn.information()
vpn.stop()- Interface names must match
utun[0-9]+, even on Linux and Windows. - Linux daemon startup requires
resolvectlinPATH, even when a session does not configure DNS. - Linux route handling uses host route replacement/deletion semantics; callers should understand the routing impact of requested routes.
- The daemon binds only to loopback addresses.
- Default WireGuard listen port is
51820whenlistenPortis not set.
Build and test Kotlin:
./gradlew :wg-kotlin:checkRun Rust daemon tests:
cargo test --manifest-path wg-kotlin-daemon-rust/Cargo.tomlRun everything:
./gradlew buildJDK 17 is required for Gradle builds.