VPN Integration Guide

Complete guide to Axe VPN admin panel and mobile application

Flutter VPN Integration

VPN Integration Overview

Learn how to integrate OpenVPN functionality into your Flutter app, manage VPN connections, handle authentication, and implement robust error handling with automatic fallback mechanisms.

VPN Setup & Dependencies

1. Required Dependencies

Add these dependencies to your pubspec.yaml:

dependencies: flutter: sdk: flutter # VPN Integration flutter_openvpn: ^2.0.0 # HTTP Client for API calls dio: ^5.3.2 # State Management provider: ^6.0.5 # Secure Storage flutter_secure_storage: ^9.0.0 # Connectivity Checking connectivity_plus: ^4.0.2 # Logging logger: ^2.0.2 # Firebase (for notifications) firebase_core: ^2.15.1 firebase_messaging: ^14.6.7 firebase_analytics: ^10.4.5 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^3.0.0

2. Platform Configuration

Android Configuration

Add VPN permissions to android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.BIND_VPN_SERVICE" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <!-- Add VPN service to application --> <service android:name="de.blinkt.openvpn.core.OpenVPNService" android:permission="android.permission.BIND_VPN_SERVICE"> <intent-filter> <action android:name="android.net.VpnService" /> </intent-filter> </service>
iOS Configuration

Add capabilities in ios/Runner/Runner.entitlements:

<key>com.apple.developer.networking.vpn.api</key> <array> <string>allow-vpn</string> </array> <key>com.apple.developer.networking.networkextension</key> <array> <string>packet-tunnel-provider</string> </array>

API Integration

1. API Client Setup

Create a robust API client for server communication:

// lib/services/api_service.dart import 'package:dio/dio.dart'; import 'package:logger/logger.dart'; class ApiService { static const String baseUrl = 'https://axe.linkze.me/api'; static const String fallbackUrl = 'http://127.0.0.1:8000/api'; late final Dio _dio; final Logger _logger = Logger(); ApiService() { _dio = Dio(BaseOptions( baseUrl: baseUrl, connectTimeout: const Duration(seconds: 10), receiveTimeout: const Duration(seconds: 15), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, )); _setupInterceptors(); } void _setupInterceptors() { _dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) { _logger.i('API Request: ${options.method} ${options.path}'); handler.next(options); }, onResponse: (response, handler) { _logger.i('API Response: ${response.statusCode} ${response.requestOptions.path}'); handler.next(response); }, onError: (error, handler) { _logger.e('API Error: ${error.message}'); handler.next(error); }, )); } // Enhanced server config fetching with retry logic Future<String> fetchServerConfig(int serverId) async { try { final response = await _dio.get( '/v1/get', queryParameters: {'id': serverId}, options: Options( validateStatus: (status) => status! < 500, ), ); if (response.statusCode == 200) { if (response.data is Map<String, dynamic>) { return response.data['config'] ?? ''; } return response.data.toString(); } else { throw Exception('Failed to load config: ${response.data['error'] ?? 'Unknown error'}'); } } on DioException catch (e) { _logger.e('DioException in fetchServerConfig: ${e.message}'); // Retry with fallback URL if (e.type == DioExceptionType.connectionTimeout || e.type == DioExceptionType.receiveTimeout) { _logger.i('Retrying with fallback URL...'); return await _retryWithFallback(serverId); } throw Exception('Network error: ${e.message}'); } } Future<String> _retryWithFallback(int serverId) async { try { final fallbackDio = Dio(BaseOptions(baseUrl: fallbackUrl)); final response = await fallbackDio.get( '/v1/get', queryParameters: {'id': serverId}, ); if (response.statusCode == 200) { if (response.data is Map<String, dynamic>) { return response.data['config'] ?? ''; } return response.data.toString(); } throw Exception('Fallback also failed'); } catch (e) { _logger.e('Fallback request failed: $e'); rethrow; } } // Get server health status Future<Map<String, dynamic>> getServerHealth(int serverId) async { try { final response = await _dio.get('/v1/server/$serverId/health'); return response.data; } catch (e) { _logger.e('Failed to get server health: $e'); rethrow; } } // Get all available servers Future<List<dynamic>> getServerList() async { try { final response = await _dio.get('/v1/list'); return List.from(response.data); } catch (e) { _logger.e('Failed to get server list: $e'); rethrow; } } }

2. Server Model

// lib/models/server.dart class VpnServer { final int id; final String countryCode; final String city; final String ip; final String protocol; final int port; final String? vpnUsername; final String? vpnPassword; final bool isFree; final int order; final bool useFile; final int? freeConnectDuration; final int connectedDevices; VpnServer({ required this.id, required this.countryCode, required this.city, required this.ip, required this.protocol, required this.port, this.vpnUsername, this.vpnPassword, required this.isFree, required this.order, required this.useFile, this.freeConnectDuration, required this.connectedDevices, }); factory VpnServer.fromJson(Map<String, dynamic> json) { return VpnServer( id: json['id'], countryCode: json['country_code'], city: json['city'], ip: json['ip'], protocol: json['protocol'], port: json['port'], vpnUsername: json['vpn_username'], vpnPassword: json['vpn_password'], isFree: json['is_free'] == 1, order: json['order'], useFile: json['use_file'] == 1, freeConnectDuration: json['free_connect_duration'], connectedDevices: json['connected_devices'], ); } String get displayName => '$city, $countryCode'; String get flagEmoji { // Convert country code to flag emoji return countryCode.toUpperCase() .split('') .map((char) => String.fromCharCode(char.codeUnitAt(0) + 0x1F1A5)) .join(''); } }

Connection Management

1. VPN Connection Service

// lib/services/vpn_service.dart import 'package:flutter_openvpn/flutter_openvpn.dart'; import 'package:logger/logger.dart'; import 'api_service.dart'; import '../models/server.dart'; enum VpnConnectionState { disconnected, connecting, connected, disconnecting, error, reconnecting, } class VpnService extends ChangeNotifier { final ApiService _apiService = ApiService(); final Logger _logger = Logger(); VpnConnectionState _state = VpnConnectionState.disconnected; VpnServer? _currentServer; String? _lastError; int _reconnectAttempts = 0; bool _usedFallbackConfig = false; // Getters VpnConnectionState get state => _state; VpnServer? get currentServer => _currentServer; String? get lastError => _lastError; bool get isConnected => _state == VpnConnectionState.connected; VpnService() { _initializeVpn(); } void _initializeVpn() { // Initialize OpenVPN and set up state listeners FlutterOpenVpn.onStateChanged.listen((state) { _handleVpnStateChange(state); }); } void _handleVpnStateChange(VpnState state) { _logger.i('VPN State changed to: $state'); switch (state) { case VpnState.connecting: _updateState(VpnConnectionState.connecting); break; case VpnState.connected: _updateState(VpnConnectionState.connected); _reconnectAttempts = 0; // Reset on successful connection break; case VpnState.disconnected: _updateState(VpnConnectionState.disconnected); break; case VpnState.noprocess: if (_state == VpnConnectionState.connected) { _logger.w('VPN process terminated unexpectedly'); _scheduleReconnect(); } break; case VpnState.error: _updateState(VpnConnectionState.error); _scheduleReconnect(); break; } } void _updateState(VpnConnectionState newState) { if (_state != newState) { _state = newState; notifyListeners(); } } // Main connection method with fallback logic Future<void> connectToServer(VpnServer server) async { _currentServer = server; _lastError = null; _usedFallbackConfig = false; _updateState(VpnConnectionState.connecting); try { // 1. Attempt primary config from server String config = await _apiService.fetchServerConfig(server.id); if (config.isNotEmpty) { await _connectWithConfig(config, server.displayName); _logConnectionAttempt(true); return; } throw Exception('Empty configuration received'); } catch (e) { _logger.e('Primary config failed: $e'); // 2. Fallback to minimal config try { final minimalConfig = _createMinimalConfig(server); _usedFallbackConfig = true; await _connectWithConfig(minimalConfig, '${server.displayName} (Fallback)'); _logConnectionAttempt(true); } catch (fallbackError) { _logger.e('Fallback config also failed: $fallbackError'); _lastError = 'Connection failed: ${fallbackError.toString()}'; _updateState(VpnConnectionState.error); _logConnectionAttempt(false); } } } Future<void> _connectWithConfig(String config, String serverName) async { await FlutterOpenVpn.connect( config: config, serverName: serverName, username: _currentServer?.vpnUsername, password: _currentServer?.vpnPassword, ); } String _createMinimalConfig(VpnServer server) { return ''' client dev tun proto ${server.protocol.toLowerCase()} remote ${server.ip} ${server.port} resolv-retry infinite nobind persist-key persist-tun verb 1 pull route-method exe route-delay 2 cipher AES-256-CBC auth SHA256 ${(server.vpnUsername?.isNotEmpty == true) ? 'auth-user-pass' : ''} ${(server.vpnUsername?.isNotEmpty == true) ? 'auth-nocache' : ''} '''; } void _scheduleReconnect() { if (_reconnectAttempts < 3 && _currentServer != null) { _reconnectAttempts++; _updateState(VpnConnectionState.reconnecting); Future.delayed(Duration(seconds: 2 * _reconnectAttempts), () { if (_state == VpnConnectionState.reconnecting) { connectToServer(_currentServer!); } }); } else { _lastError = 'Maximum reconnection attempts reached'; _updateState(VpnConnectionState.error); } } Future<void> disconnect() async { _updateState(VpnConnectionState.disconnecting); await FlutterOpenVpn.disconnect(); _currentServer = null; } void _logConnectionAttempt(bool success) { // Log to Firebase Analytics if (_currentServer != null) { // FirebaseAnalytics.instance.logEvent( // name: 'vpn_connection', // parameters: { // 'server_id': _currentServer!.id, // 'success': success, // 'used_fallback': _usedFallbackConfig, // }, // ); } } }

Error Handling & Fallback

1. Connection Error Types

Network Errors
  • Connection Timeout: Server unreachable
  • DNS Resolution: Domain lookup failed
  • SSL/TLS Errors: Certificate issues
  • Firewall Block: Port blocked
Configuration Errors
  • Invalid Config: Malformed OVPN
  • Auth Failure: Wrong credentials
  • Missing Certs: No certificates
  • Protocol Mismatch: UDP/TCP issues

2. Fallback Strategy Implementation

// lib/services/connection_fallback.dart class ConnectionFallbackService { static const List<Map<String, dynamic>> fallbackConfigs = [ {'protocol': 'udp', 'port': 1194}, {'protocol': 'tcp', 'port': 443}, {'protocol': 'tcp', 'port': 80}, {'protocol': 'udp', 'port': 53}, ]; static String generateFallbackConfig(VpnServer server, int attemptIndex) { final config = fallbackConfigs[attemptIndex % fallbackConfigs.length]; return ''' client dev tun proto ${config['protocol']} remote ${server.ip} ${config['port']} resolv-retry infinite nobind persist-key persist-tun verb 1 pull route-method exe route-delay 2 cipher AES-256-CBC auth SHA256 comp-lzo fast-io script-security 2 redirect-gateway def1 bypass-dhcp dhcp-option DNS 8.8.8.8 dhcp-option DNS 8.8.4.4 '''; } static Future<bool> testServerConnectivity(String ip, int port) async { try { final socket = await Socket.connect(ip, port, timeout: Duration(seconds: 5)); socket.destroy(); return true; } catch (e) { return false; } } }

Connection Monitoring

1. Real-time Connection Monitoring

// lib/services/connection_monitor.dart class ConnectionMonitor { static const Duration checkInterval = Duration(seconds: 10); Timer? _monitorTimer; final VpnService _vpnService; final Logger _logger = Logger(); ConnectionMonitor(this._vpnService); void startMonitoring() { _monitorTimer?.cancel(); _monitorTimer = Timer.periodic(checkInterval, (timer) { if (_vpnService.isConnected) { _checkConnectionHealth(); } }); } void stopMonitoring() { _monitorTimer?.cancel(); } Future<void> _checkConnectionHealth() async { try { // Check actual internet connectivity through VPN final response = await http.get( Uri.parse('https://httpbin.org/ip'), headers: {'timeout': '5'}, ).timeout(Duration(seconds: 5)); if (response.statusCode == 200) { final data = jsonDecode(response.body); final currentIp = data['origin']; // Verify IP has changed (indicating VPN is working) if (currentIp != null) { _logger.i('VPN connection healthy, IP: $currentIp'); } } } catch (e) { _logger.w('Connection health check failed: $e'); // Optionally trigger reconnection } } }

2. Usage Statistics Tracking

// lib/services/usage_tracker.dart class UsageTracker { static const String _connectionTimeKey = 'total_connection_time'; static const String _dataUsageKey = 'total_data_usage'; DateTime? _connectionStartTime; int _totalConnectionTime = 0; void startSession() { _connectionStartTime = DateTime.now(); _loadStoredData(); } void endSession() { if (_connectionStartTime != null) { final duration = DateTime.now().difference(_connectionStartTime!); _totalConnectionTime += duration.inSeconds; _saveConnectionTime(); _connectionStartTime = null; } } Future<void> _loadStoredData() async { final prefs = await SharedPreferences.getInstance(); _totalConnectionTime = prefs.getInt(_connectionTimeKey) ?? 0; } Future<void> _saveConnectionTime() async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt(_connectionTimeKey, _totalConnectionTime); } String get formattedTotalTime { final hours = _totalConnectionTime ~/ 3600; final minutes = (_totalConnectionTime % 3600) ~/ 60; return '${hours}h ${minutes}m'; } }

Best Practices

Do's
  • Always validate server configs
  • Implement automatic reconnection
  • Set appropriate timeouts
  • Log all connection attempts
  • Handle background/foreground transitions
  • Optimize for battery usage
Don'ts
  • Don't ignore connection errors
  • Don't store credentials in plain text
  • Don't skip SSL certificate validation
  • Don't make unlimited reconnection attempts
  • Don't block the UI during connections
  • Don't forget to clean up resources
Next Steps

You've learned VPN integration fundamentals. Continue with these advanced topics: