ikev2 0.0.1
ikev2: ^0.0.1 copied to clipboard
Flutter plugin for IKEv2 VPN protocol support.
example/lib/main.dart
/// Copyright (C) 2026 Orban
///
/// This library is free software; you can redistribute it and/or
/// modify it under the terms of the GNU Lesser General Public
/// License as published by the Free Software Foundation; either
/// version 2.1 of the License, or (at your option) any later version.
///
/// This library is distributed in the hope that it will be useful,
/// but WITHOUT ANY WARRANTY; without even the implied warranty of
/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
/// Lesser General Public License for more details.
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:ikev2/ikev2.dart';
import 'package:ikev2/state.dart';
import 'package:ikev2/vpn_traffic_stats.dart';
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _addressController = TextEditingController(text: "");
final _usernameController = TextEditingController(text: "");
final _passwordController = TextEditingController(text: "");
final _secretController = TextEditingController(text: "");
final _remoteId = TextEditingController(text: "");
final _localId = TextEditingController(text: "");
var state = IkeV2State.disconnected;
CharonErrorState? charonState = CharonErrorState.NO_ERROR;
VpnTrafficStats? trafficStats;
@override
void initState() {
super.initState();
// Always call prepare (for Android)
IkeV2.prepare();
// ✅ Restore current VPN state
IkeV2.currentState.then((s) {
setState(() {
state = s;
});
// ✅ If VPN is already connected, start monitoring manually
if (s == IkeV2State.connected) {
IkeV2.startTrafficMonitor(); // Start native side monitor
IkeV2.onTrafficChanged.listen((stats) {
setState(() {
trafficStats = stats;
});
debugPrint('⬇ Download: ${stats.downloadSpeed} B/s');
debugPrint('⬆ Upload: ${stats.uploadSpeed} B/s');
debugPrint('↓ Total Download: ${stats.totalDownload} bytes');
debugPrint('↑ Total Upload: ${stats.totalUpload} bytes');
debugPrint('⏱️ Duration: ${stats.duration} seconds');
});
}
});
// ✅ Restore Charon error state
IkeV2.charonErrorState.then((s) {
setState(() {
charonState = s ?? CharonErrorState.NO_ERROR;
});
});
// ✅ VPN connection state listener
IkeV2.onStateChanged.listen((s) {
setState(() {
state = s;
});
if (s == IkeV2State.connected) {
IkeV2.startTrafficMonitor(); // Start on connection too
IkeV2.onTrafficChanged.listen((stats) {
setState(() {
trafficStats = stats;
});
// print('⬇ Download: ${stats.downloadSpeed} B/s');
// print('⬆ Upload: ${stats.uploadSpeed} B/s');
// print('↓ Total Download: ${stats.totalDownload} bytes');
// print('↑ Total Upload: ${stats.totalUpload} bytes');
// print('⏱️ Duration: ${stats.duration} seconds');
});
} else {
setState(() {
trafficStats = null;
});
}
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter VPN'),
),
body: ListView(
padding: const EdgeInsets.all(12),
children: <Widget>[
Text('Current State: $state'),
Text('Current Charon State: $charonState'),
if (trafficStats != null) ...[
Text('⬇ Download Speed: ${trafficStats!.downloadSpeed} B/s'),
Text('⬆ Upload Speed: ${trafficStats!.uploadSpeed} B/s'),
Text('↓ Total Download: ${trafficStats!.totalDownload} bytes'),
Text('↑ Total Upload: ${trafficStats!.totalUpload} bytes'),
Text('⏱️ Duration: ${trafficStats!.duration} seconds'),
Text('⏱️ Timer: ${trafficStats!.formattedDuration}'),
],
TextFormField(
controller: _addressController,
decoration: const InputDecoration(
icon: Icon(Icons.map_outlined),
labelText: "Server Address",
),
),
TextFormField(
controller: _usernameController,
decoration: const InputDecoration(
icon: Icon(Icons.person_outline),
labelText: "Username",
),
),
TextFormField(
controller: _passwordController,
obscureText: true,
decoration: const InputDecoration(
icon: Icon(Icons.lock_outline),
labelText: "Password",
),
),
TextFormField(
controller: _secretController,
obscureText: true,
decoration: const InputDecoration(
icon: Icon(Icons.vpn_key_outlined),
labelText: 'Secret / PSK',
),
),
TextFormField(
controller: _remoteId,
decoration: const InputDecoration(
icon: Icon(Icons.cloud_circle_outlined),
labelText: 'Remote ID (optional)',
),
),
TextFormField(
controller: _localId,
decoration: const InputDecoration(
icon: Icon(Icons.cloud_circle_outlined),
labelText: 'Local ID (optional)',
),
),
ElevatedButton(
child: const Text('Connect IKEv2 EAP'),
onPressed: () => IkeV2.connectIkev2EAP(
server: _addressController.text,
username: _usernameController.text,
password: _passwordController.text,
remoteId: _remoteId.text.isNotEmpty ? _remoteId.text : null,
localId: _localId.text.isNotEmpty ? _localId.text : null,
disableCertValidation:
false, // Keep false as default for better chain loading
),
),
ElevatedButton(
child: const Text('Connect IKEv2 PSK'),
onPressed: () => IkeV2.connectIkev2Psk(
server: _addressController.text,
username: _usernameController.text,
password: _passwordController.text,
remoteId: _remoteId.text.isNotEmpty ? _remoteId.text : '',
localId: _localId.text.isNotEmpty ? _localId.text : '',
// In PSK mode, 'secret' is effectively the PSK, but in this library generic structure
// it might be passed differently. Looking at library code:
// connectIkev2Psk does NOT take a 'secret' param directly in the signature shown in previous `read_file`.
// Wait, let me check the library definition again. `connectIkev2Psk` signature:
// required String server, required String username, required String password, required String remoteId, required String localId, ...
// It seems it reuses 'password' as PSK or expects it elsewhere?
// Checking previous `FlutterVpn` (now `IkeV2`) definition:
// connectIkev2Psk(...) => ... 'Password': password ... 'Secret': '', ...
// It seems `connectIkev2Psk` in `ikev2.dart` takes `password` but hardcodes `Secret` to empty string.
// However, usually PSK is passed as the shared secret.
// Let's check `connectIPSec`. It takes `secret`.
// If the user wants generic IKEv2 PSK, typically that's Authentication Method = PSK.
// The library method `connectIkev2Psk` takes username/password? That is unusual for pure PSK.
// It might be EAP with specific config or the library naming is specific.
// For `connectIPSec`, it takes `secret`.
// Let's implement based on available methods.
),
),
ElevatedButton(
child: const Text('Disconnect'),
onPressed: () => IkeV2.disconnect(),
),
ElevatedButton(
child: const Text('Update State'),
onPressed: () async {
var newState = await IkeV2.currentState;
setState(() => state = newState);
},
),
ElevatedButton(
child: const Text('Update Charon State'),
onPressed: () async {
var newState = await IkeV2.charonErrorState;
setState(() => charonState = newState);
},
),
],
),
),
);
}
}