├── ios ├── Assets │ └── .gitkeep ├── Classes │ ├── FlutterVpnPlugin.h │ ├── FlutterVpnPlugin.m │ ├── VPNStateHandler.swift │ ├── SwiftFlutterVpnPlugin.swift │ └── KeychainService.swift ├── .gitignore └── flutter_vpn.podspec ├── android ├── settings.gradle ├── proguard-rules.pro ├── src │ └── main │ │ ├── res │ │ ├── drawable-hdpi │ │ │ ├── ic_notification.png │ │ │ ├── ic_notification_warning.png │ │ │ └── ic_notification_connecting.png │ │ ├── drawable-mdpi │ │ │ ├── ic_notification.png │ │ │ ├── ic_notification_warning.png │ │ │ └── ic_notification_connecting.png │ │ ├── drawable-xhdpi │ │ │ ├── ic_notification.png │ │ │ ├── ic_notification_warning.png │ │ │ └── ic_notification_connecting.png │ │ └── values │ │ │ ├── colors.xml │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── org │ │ │ └── strongswan │ │ │ └── android │ │ │ ├── logic │ │ │ ├── imc │ │ │ │ ├── attributes │ │ │ │ │ ├── Attribute.java │ │ │ │ │ ├── DeviceIdAttribute.java │ │ │ │ │ ├── PrivateEnterpriseNumber.java │ │ │ │ │ ├── ProductInformationAttribute.java │ │ │ │ │ ├── PortFilterAttribute.java │ │ │ │ │ ├── StringVersionAttribute.java │ │ │ │ │ ├── InstalledPackagesAttribute.java │ │ │ │ │ ├── SettingsAttribute.java │ │ │ │ │ └── AttributeType.java │ │ │ │ ├── collectors │ │ │ │ │ ├── Collector.java │ │ │ │ │ ├── ProductInformationCollector.java │ │ │ │ │ ├── StringVersionCollector.java │ │ │ │ │ ├── DeviceIdCollector.java │ │ │ │ │ ├── Protocol.java │ │ │ │ │ ├── InstalledPackagesCollector.java │ │ │ │ │ ├── SettingsCollector.java │ │ │ │ │ └── PortFilterCollector.java │ │ │ │ ├── ImcState.java │ │ │ │ ├── AndroidImc.java │ │ │ │ └── RemediationInstruction.java │ │ │ ├── SimpleFetcher.java │ │ │ ├── Scheduler.java │ │ │ ├── NetworkManager.java │ │ │ └── TrustedCertificateManager.java │ │ │ ├── utils │ │ │ ├── Constants.java │ │ │ ├── Utils.java │ │ │ ├── SettingsWriter.java │ │ │ ├── IPRangeSet.java │ │ │ └── BufferedByteWriter.java │ │ │ └── data │ │ │ └── VpnType.java │ │ └── kotlin │ │ └── io │ │ └── xdea │ │ └── flutter_vpn │ │ ├── VpnStateHandler.kt │ │ └── FlutterVpnPlugin.kt ├── .gitignore └── build.gradle ├── example ├── ios │ ├── Runner │ │ ├── Runner-Bridging-Header.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ ├── Podfile.lock │ ├── .gitignore │ └── Podfile ├── android │ ├── gradle.properties │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── drawable-v21 │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── values │ │ │ │ │ │ └── styles.xml │ │ │ │ │ └── values-night │ │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── io │ │ │ │ │ │ └── xdea │ │ │ │ │ │ └── flutter_vpn_example │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── settings.gradle │ └── build.gradle ├── README.md ├── .gitignore ├── test │ └── widget_test.dart ├── analysis_options.yaml ├── pubspec.yaml ├── lib │ └── main.dart └── pubspec.lock ├── analysis_options.yaml ├── .gitignore ├── flutter_vpn.iml ├── lib ├── state.dart ├── flutter_vpn.dart ├── flutter_vpn_platform_interface.dart └── flutter_vpn_method_channel.dart ├── .metadata ├── README.md ├── CHANGELOG.md ├── pubspec.yaml └── LICENSE /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'flutter_vpn' 2 | -------------------------------------------------------------------------------- /android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class org.strongswan.android.logic.* {*;} -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /ios/Classes/FlutterVpnPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface FlutterVpnPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/src/main/res/drawable-hdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/android/src/main/res/drawable-hdpi/ic_notification.png -------------------------------------------------------------------------------- /android/src/main/res/drawable-mdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/android/src/main/res/drawable-mdpi/ic_notification.png -------------------------------------------------------------------------------- /android/src/main/res/drawable-xhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/android/src/main/res/drawable-xhdpi/ic_notification.png -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .cxx 10 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/src/main/res/drawable-hdpi/ic_notification_warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/android/src/main/res/drawable-hdpi/ic_notification_warning.png -------------------------------------------------------------------------------- /android/src/main/res/drawable-mdpi/ic_notification_warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/android/src/main/res/drawable-mdpi/ic_notification_warning.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/src/main/res/drawable-hdpi/ic_notification_connecting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/android/src/main/res/drawable-hdpi/ic_notification_connecting.png -------------------------------------------------------------------------------- /android/src/main/res/drawable-mdpi/ic_notification_connecting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/android/src/main/res/drawable-mdpi/ic_notification_connecting.png -------------------------------------------------------------------------------- /android/src/main/res/drawable-xhdpi/ic_notification_warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/android/src/main/res/drawable-xhdpi/ic_notification_warning.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/src/main/res/drawable-xhdpi/ic_notification_connecting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/android/src/main/res/drawable-xhdpi/ic_notification_connecting.png -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-dea/flutter_vpn/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/io/xdea/flutter_vpn_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package io.xdea.flutter_vpn_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip 7 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_vpn (0.1.0): 4 | - Flutter 5 | 6 | DEPENDENCIES: 7 | - Flutter (from `Flutter`) 8 | - flutter_vpn (from `.symlinks/plugins/flutter_vpn/ios`) 9 | 10 | EXTERNAL SOURCES: 11 | Flutter: 12 | :path: Flutter 13 | flutter_vpn: 14 | :path: ".symlinks/plugins/flutter_vpn/ios" 15 | 16 | SPEC CHECKSUMS: 17 | Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a 18 | flutter_vpn: a106f778c4994b07edc78c6a66ca24a7b539bb15 19 | 20 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c 21 | 22 | COCOAPODS: 1.11.3 23 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/ephemeral/ 38 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /ios/Classes/FlutterVpnPlugin.m: -------------------------------------------------------------------------------- 1 | #import "FlutterVpnPlugin.h" 2 | #if __has_include() 3 | #import 4 | #else 5 | // Support project import fallback if the generated compatibility header 6 | // is not copied when this plugin is created as a library. 7 | // https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 8 | #import "flutter_vpn-Swift.h" 9 | #endif 10 | 11 | @implementation FlutterVpnPlugin 12 | + (void)registerWithRegistrar:(NSObject*)registrar { 13 | [SwiftFlutterVpnPlugin registerWithRegistrar:registrar]; 14 | } 15 | @end 16 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # flutter_vpn_example 2 | 3 | Demonstrates how to use the flutter_vpn plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) 13 | 14 | For help getting started with Flutter development, view the 15 | [online documentation](https://docs.flutter.dev/), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | .packages 30 | build/ 31 | android/src/main/libs 32 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.1.3' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /android/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | #D9192C 18 | #FF9909 19 | #99CC00 20 | 21 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Android Studio will place build artifacts here 45 | /android/app/debug 46 | /android/app/profile 47 | /android/app/release 48 | -------------------------------------------------------------------------------- /ios/flutter_vpn.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint flutter_vpn.podspec` to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'flutter_vpn' 7 | s.version = '0.1.0' 8 | s.summary = 'Flutter plugin to access vpn service.' 9 | s.description = <<-DESC 10 | Flutter plugin to access vpn service. 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.dependency 'Flutter' 18 | s.platform = :ios, '8.0' 19 | 20 | # Flutter.framework does not contain a i386 slice. 21 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } 22 | s.swift_version = '5.0' 23 | end 24 | -------------------------------------------------------------------------------- /flutter_vpn.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_vpn_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => widget is Text && 22 | widget.data!.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/attributes/Attribute.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * HSR Hochschule fuer Technik Rapperswil 4 | * 5 | * This program is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License as published by the 7 | * Free Software Foundation; either version 2 of the License, or (at your 8 | * option) any later version. See . 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | */ 15 | 16 | package org.strongswan.android.logic.imc.attributes; 17 | 18 | /** 19 | * Interface to be implemented by attribute classes 20 | */ 21 | public interface Attribute 22 | { 23 | /** 24 | * Returns the binary encoding of the attribute 25 | * @return binary encoding 26 | */ 27 | public byte[] getEncoding(); 28 | } 29 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/state.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2018-2022 Jason C.H 2 | /// 3 | /// This library is free software; you can redistribute it and/or 4 | /// modify it under the terms of the GNU Lesser General Public 5 | /// License as published by the Free Software Foundation; either 6 | /// version 2.1 of the License, or (at your option) any later version. 7 | /// 8 | /// This library is distributed in the hope that it will be useful, 9 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | /// Lesser General Public License for more details. 12 | 13 | // ignore_for_file: constant_identifier_names 14 | 15 | /// The generic VPN state for all platforms. 16 | enum FlutterVpnState { 17 | disconnected, 18 | connecting, 19 | connected, 20 | disconnecting, 21 | error, 22 | } 23 | 24 | /// The error state from `VpnStateService`. 25 | /// Only available for Android. 26 | enum CharonErrorState { 27 | NO_ERROR, 28 | AUTH_FAILED, 29 | PEER_AUTH_FAILED, 30 | LOOKUP_FAILED, 31 | UNREACHABLE, 32 | GENERIC_ERROR, 33 | PASSWORD_MISSING, 34 | CERTIFICATE_UNAVAILABLE, 35 | } 36 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/collectors/Collector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * HSR Hochschule fuer Technik Rapperswil 4 | * 5 | * This program is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License as published by the 7 | * Free Software Foundation; either version 2 of the License, or (at your 8 | * option) any later version. See . 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | */ 15 | 16 | package org.strongswan.android.logic.imc.collectors; 17 | 18 | import org.strongswan.android.logic.imc.attributes.Attribute; 19 | 20 | /** 21 | * Interface for measurement collectors 22 | */ 23 | public interface Collector 24 | { 25 | /** 26 | * This method shall return the result of a measurement, if available 27 | * @return attribute or null 28 | */ 29 | public abstract Attribute getMeasurement(); 30 | } 31 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: 8661d8aecd626f7f57ccbcb735553edc05a2e713 8 | channel: beta 9 | 10 | project_type: plugin 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 13a2fb10b838971ce211230f8ffdd094c14af02c 17 | base_revision: 13a2fb10b838971ce211230f8ffdd094c14af02c 18 | - platform: android 19 | create_revision: 13a2fb10b838971ce211230f8ffdd094c14af02c 20 | base_revision: 13a2fb10b838971ce211230f8ffdd094c14af02c 21 | - platform: ios 22 | create_revision: 13a2fb10b838971ce211230f8ffdd094c14af02c 23 | base_revision: 13a2fb10b838971ce211230f8ffdd094c14af02c 24 | 25 | # User provided section 26 | 27 | # List of Local paths (relative to this file) that should be 28 | # ignored by the migrate tool. 29 | # 30 | # Files that are not part of the templates will be ignored by default. 31 | unmanaged_files: 32 | - 'lib/main.dart' 33 | - 'ios/Runner.xcodeproj/project.pbxproj' 34 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/collectors/ProductInformationCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * Copyright (C) 2012 Christoph Buehler 4 | * Copyright (C) 2012 Patrick Loetscher 5 | * HSR Hochschule fuer Technik Rapperswil 6 | * 7 | * This program is free software; you can redistribute it and/or modify it 8 | * under the terms of the GNU General Public License as published by the 9 | * Free Software Foundation; either version 2 of the License, or (at your 10 | * option) any later version. See . 11 | * 12 | * This program is distributed in the hope that it will be useful, but 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 14 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 | * for more details. 16 | */ 17 | 18 | package org.strongswan.android.logic.imc.collectors; 19 | 20 | import org.strongswan.android.logic.imc.attributes.Attribute; 21 | import org.strongswan.android.logic.imc.attributes.ProductInformationAttribute; 22 | 23 | public class ProductInformationCollector implements Collector 24 | { 25 | @Override 26 | public Attribute getMeasurement() 27 | { /* this is currently hardcoded in the attribute */ 28 | return new ProductInformationAttribute(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/collectors/StringVersionCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * Copyright (C) 2012 Christoph Buehler 4 | * Copyright (C) 2012 Patrick Loetscher 5 | * HSR Hochschule fuer Technik Rapperswil 6 | * 7 | * This program is free software; you can redistribute it and/or modify it 8 | * under the terms of the GNU General Public License as published by the 9 | * Free Software Foundation; either version 2 of the License, or (at your 10 | * option) any later version. See . 11 | * 12 | * This program is distributed in the hope that it will be useful, but 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 14 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 | * for more details. 16 | */ 17 | 18 | package org.strongswan.android.logic.imc.collectors; 19 | 20 | import org.strongswan.android.logic.imc.attributes.Attribute; 21 | import org.strongswan.android.logic.imc.attributes.StringVersionAttribute; 22 | 23 | public class StringVersionCollector implements Collector 24 | { 25 | @Override 26 | public Attribute getMeasurement() 27 | { 28 | StringVersionAttribute attribute = new StringVersionAttribute(); 29 | attribute.setProductVersionNumber(android.os.Build.VERSION.RELEASE); 30 | attribute.setInternalBuildNumber(android.os.Build.DISPLAY); 31 | return attribute; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /ios/Classes/VPNStateHandler.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2019 Jerry Wang, Jason C.H 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | import Foundation 16 | 17 | class VPNStateHandler: FlutterStreamHandler { 18 | static var _sink: FlutterEventSink? 19 | 20 | static func updateState(_ newState: Int, errorMessage: String? = nil) { 21 | guard let sink = _sink else { 22 | return 23 | } 24 | 25 | if let errorMsg = errorMessage { 26 | sink(FlutterError(code: "\(newState)", 27 | message: errorMsg, 28 | details: nil)) 29 | return 30 | } 31 | 32 | sink(newState) 33 | } 34 | 35 | func onListen(withArguments _: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { 36 | VPNStateHandler._sink = events 37 | return nil 38 | } 39 | 40 | func onCancel(withArguments _: Any?) -> FlutterError? { 41 | VPNStateHandler._sink = nil 42 | return nil 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/ImcState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * HSR Hochschule fuer Technik Rapperswil 4 | * 5 | * This program is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License as published by the 7 | * Free Software Foundation; either version 2 of the License, or (at your 8 | * option) any later version. See . 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | */ 15 | 16 | package org.strongswan.android.logic.imc; 17 | 18 | public enum ImcState 19 | { 20 | UNKNOWN(0), 21 | ALLOW(1), 22 | BLOCK(2), 23 | ISOLATE(3); 24 | 25 | private final int mValue; 26 | 27 | private ImcState(int value) 28 | { 29 | mValue = value; 30 | } 31 | 32 | /** 33 | * Get the numeric value of the IMC state. 34 | * @return numeric value 35 | */ 36 | public int getValue() 37 | { 38 | return mValue; 39 | } 40 | 41 | /** 42 | * Get the enum entry from a numeric value, if defined 43 | * 44 | * @param value numeric value 45 | * @return the enum entry or null 46 | */ 47 | public static ImcState fromValue(int value) 48 | { 49 | for (ImcState state : ImcState.values()) 50 | { 51 | if (state.mValue == value) 52 | { 53 | return state; 54 | } 55 | } 56 | return null; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/attributes/DeviceIdAttribute.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * HSR Hochschule fuer Technik Rapperswil 4 | * 5 | * This program is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License as published by the 7 | * Free Software Foundation; either version 2 of the License, or (at your 8 | * option) any later version. See . 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | */ 15 | 16 | package org.strongswan.android.logic.imc.attributes; 17 | 18 | /** 19 | * ITA Device ID attribute 20 | * 21 | * 1 2 3 22 | * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 23 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 24 | * | Device ID (Variable Length) | 25 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 26 | */ 27 | public class DeviceIdAttribute implements Attribute 28 | { 29 | private String mDeviceId; 30 | 31 | /** 32 | * Set the device ID 33 | * @param version version number 34 | */ 35 | public void setDeviceId(String deviceId) 36 | { 37 | this.mDeviceId = deviceId; 38 | } 39 | 40 | @Override 41 | public byte[] getEncoding() 42 | { 43 | return mDeviceId.getBytes(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/collectors/DeviceIdCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * HSR Hochschule fuer Technik Rapperswil 4 | * 5 | * This program is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License as published by the 7 | * Free Software Foundation; either version 2 of the License, or (at your 8 | * option) any later version. See . 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | */ 15 | 16 | package org.strongswan.android.logic.imc.collectors; 17 | 18 | import org.strongswan.android.logic.imc.attributes.Attribute; 19 | import org.strongswan.android.logic.imc.attributes.DeviceIdAttribute; 20 | 21 | import android.content.ContentResolver; 22 | import android.content.Context; 23 | 24 | public class DeviceIdCollector implements Collector 25 | { 26 | private final ContentResolver mContentResolver; 27 | 28 | public DeviceIdCollector(Context context) 29 | { 30 | mContentResolver = context.getContentResolver(); 31 | } 32 | 33 | @Override 34 | public Attribute getMeasurement() 35 | { 36 | String id = android.provider.Settings.Secure.getString(mContentResolver, "android_id"); 37 | if (id != null) 38 | { 39 | DeviceIdAttribute attribute = new DeviceIdAttribute(); 40 | attribute.setDeviceId(id); 41 | return attribute; 42 | } 43 | return null; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/collectors/Protocol.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * HSR Hochschule fuer Technik Rapperswil 4 | * 5 | * This program is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License as published by the 7 | * Free Software Foundation; either version 2 of the License, or (at your 8 | * option) any later version. See . 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | */ 15 | 16 | package org.strongswan.android.logic.imc.collectors; 17 | 18 | public enum Protocol 19 | { 20 | TCP((byte)6, "tcp", "tcp6"), 21 | UDP((byte)17, "udp", "udp6"); 22 | 23 | private final byte mValue; 24 | private String[] mNames; 25 | 26 | private Protocol(byte value, String... names) 27 | { 28 | mValue = value; 29 | mNames = names; 30 | } 31 | 32 | /** 33 | * Get the numeric value of the protocol. 34 | * @return numeric value 35 | */ 36 | public byte getValue() 37 | { 38 | return mValue; 39 | } 40 | 41 | /** 42 | * Get the protocol from the given protocol name, if found. 43 | * @param name protocol name (e.g. "udp" or "tcp") 44 | * @return enum entry or null 45 | */ 46 | public static Protocol fromName(String name) 47 | { 48 | for (Protocol protocol : Protocol.values()) 49 | { 50 | for (String keyword : protocol.mNames) 51 | { 52 | if (keyword.equalsIgnoreCase(name)) 53 | { 54 | return protocol; 55 | } 56 | } 57 | } 58 | return null; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/attributes/PrivateEnterpriseNumber.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * HSR Hochschule fuer Technik Rapperswil 4 | * 5 | * This program is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License as published by the 7 | * Free Software Foundation; either version 2 of the License, or (at your 8 | * option) any later version. See . 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | */ 15 | 16 | package org.strongswan.android.logic.imc.attributes; 17 | 18 | public enum PrivateEnterpriseNumber 19 | { 20 | IETF(0x000000), 21 | GOOGLE(0x002B79), 22 | ITA(0x00902a), 23 | UNASSIGNED(0xfffffe), 24 | RESERVED(0xffffff); 25 | 26 | private int mValue; 27 | 28 | /** 29 | * Enum for private enterprise numbers (PEN) as allocated by IANA 30 | * 31 | * @param value numeric value 32 | */ 33 | private PrivateEnterpriseNumber(int value) 34 | { 35 | mValue = value; 36 | } 37 | 38 | /** 39 | * Get the numeric value of a PEN 40 | * 41 | * @return numeric value 42 | */ 43 | public int getValue() 44 | { 45 | return mValue; 46 | } 47 | 48 | /** 49 | * Get the enum entry from a numeric value, if defined 50 | * 51 | * @param value numeric value 52 | * @return the enum entry or null 53 | */ 54 | public static PrivateEnterpriseNumber fromValue(int value) 55 | { 56 | for (PrivateEnterpriseNumber pen : PrivateEnterpriseNumber.values()) 57 | { 58 | if (pen.mValue == value) 59 | { 60 | return pen; 61 | } 62 | } 63 | return null; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Flutter Vpn 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | flutter_vpn_example 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 10 | 18 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/attributes/ProductInformationAttribute.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * Copyright (C) 2012 Christoph Buehler 4 | * Copyright (C) 2012 Patrick Loetscher 5 | * HSR Hochschule fuer Technik Rapperswil 6 | * 7 | * This program is free software; you can redistribute it and/or modify it 8 | * under the terms of the GNU General Public License as published by the 9 | * Free Software Foundation; either version 2 of the License, or (at your 10 | * option) any later version. See . 11 | * 12 | * This program is distributed in the hope that it will be useful, but 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 14 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 | * for more details. 16 | */ 17 | 18 | package org.strongswan.android.logic.imc.attributes; 19 | 20 | import org.strongswan.android.utils.BufferedByteWriter; 21 | 22 | /** 23 | * PA-TNC Product Information attribute (see section 4.2.2 of RFC 5792) 24 | * 25 | * 1 2 3 26 | * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 27 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 28 | * | Product Vendor ID | Product ID | 29 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 30 | * | Product ID | Product Name (Variable Length) | 31 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 32 | */ 33 | public class ProductInformationAttribute implements Attribute 34 | { 35 | private final String PRODUCT_NAME = "Android"; 36 | private final short PRODUCT_ID = 0; 37 | 38 | @Override 39 | public byte[] getEncoding() 40 | { 41 | BufferedByteWriter writer = new BufferedByteWriter(); 42 | writer.put24(PrivateEnterpriseNumber.GOOGLE.getValue()); 43 | writer.put16(PRODUCT_ID); 44 | writer.put(PRODUCT_NAME.getBytes()); 45 | return writer.toByteArray(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/collectors/InstalledPackagesCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * Copyright (C) 2012 Christoph Buehler 4 | * Copyright (C) 2012 Patrick Loetscher 5 | * HSR Hochschule fuer Technik Rapperswil 6 | * 7 | * This program is free software; you can redistribute it and/or modify it 8 | * under the terms of the GNU General Public License as published by the 9 | * Free Software Foundation; either version 2 of the License, or (at your 10 | * option) any later version. See . 11 | * 12 | * This program is distributed in the hope that it will be useful, but 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 14 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 | * for more details. 16 | */ 17 | 18 | package org.strongswan.android.logic.imc.collectors; 19 | 20 | import java.util.List; 21 | 22 | import org.strongswan.android.logic.imc.attributes.Attribute; 23 | import org.strongswan.android.logic.imc.attributes.InstalledPackagesAttribute; 24 | 25 | import android.content.Context; 26 | import android.content.pm.ApplicationInfo; 27 | import android.content.pm.PackageInfo; 28 | import android.content.pm.PackageManager; 29 | 30 | public class InstalledPackagesCollector implements Collector 31 | { 32 | private final PackageManager mPackageManager; 33 | 34 | public InstalledPackagesCollector(Context context) 35 | { 36 | mPackageManager = context.getPackageManager(); 37 | } 38 | 39 | @Override 40 | public Attribute getMeasurement() 41 | { 42 | InstalledPackagesAttribute attribute = new InstalledPackagesAttribute(); 43 | List packages = mPackageManager.getInstalledPackages(0); 44 | for (PackageInfo info : packages) 45 | { 46 | if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 || 47 | info.packageName == null || info.versionName == null) 48 | { /* ignore packages installed in the system image */ 49 | continue; 50 | } 51 | attribute.addPackage(info.packageName, info.versionName); 52 | } 53 | return attribute; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/collectors/SettingsCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * Copyright (C) 2012 Christoph Buehler 4 | * Copyright (C) 2012 Patrick Loetscher 5 | * HSR Hochschule fuer Technik Rapperswil 6 | * 7 | * This program is free software; you can redistribute it and/or modify it 8 | * under the terms of the GNU General Public License as published by the 9 | * Free Software Foundation; either version 2 of the License, or (at your 10 | * option) any later version. See . 11 | * 12 | * This program is distributed in the hope that it will be useful, but 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 14 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 | * for more details. 16 | */ 17 | 18 | package org.strongswan.android.logic.imc.collectors; 19 | 20 | import java.util.Locale; 21 | 22 | import org.strongswan.android.logic.imc.attributes.Attribute; 23 | import org.strongswan.android.logic.imc.attributes.SettingsAttribute; 24 | 25 | import android.content.ContentResolver; 26 | import android.content.Context; 27 | 28 | public class SettingsCollector implements Collector 29 | { 30 | private final ContentResolver mContentResolver; 31 | private final String[] mSettings; 32 | 33 | public SettingsCollector(Context context, String[] args) 34 | { 35 | mContentResolver = context.getContentResolver(); 36 | mSettings = args; 37 | } 38 | 39 | @Override 40 | public Attribute getMeasurement() 41 | { 42 | if (mSettings == null || mSettings.length == 0) 43 | { 44 | return null; 45 | } 46 | SettingsAttribute attribute = new SettingsAttribute(); 47 | for (String name : mSettings) 48 | { 49 | String value = android.provider.Settings.Secure.getString(mContentResolver, name.toLowerCase(Locale.US)); 50 | if (value == null) 51 | { 52 | value = android.provider.Settings.System.getString(mContentResolver, name.toLowerCase(Locale.US)); 53 | } 54 | if (value != null) 55 | { 56 | attribute.addSetting(name, value); 57 | } 58 | } 59 | return attribute; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter VPN plugin 2 | 3 | 4 | Pub Package 6 | 7 | 8 | Awesome Flutter 9 | 10 | 11 | This plugin help developers to access VPN service in their flutter app. 12 | 本插件帮助开发者在自己的应用内调用 VPN 服务。 13 | 14 | The Android part was implemented by [strongswan](https://www.strongswan.org/) which support ikev2 protocol. 15 | The iOS part was implemented by NEVPNManager. 16 | 17 | Issues and PRs are welcome! 18 | 19 | ## Installation 20 | 21 | ### For Android 22 | 23 | Modify your `app/build.gradle` to use abiFilter since flutter doesn't apply abiFilter for target platform yet. 24 | ```gradle 25 | android { 26 | ... 27 | buildTypes { 28 | ... 29 | release { 30 | ... 31 | ndk { 32 | if (!project.hasProperty('target-platform')) { 33 | abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64' 34 | } else { 35 | def platforms = project.property('target-platform').split(',') 36 | def platformMap = [ 37 | 'android-arm' : 'armeabi-v7a', 38 | 'android-arm64': 'arm64-v8a', 39 | 'android-x86' : 'x86', 40 | 'android-x64' : 'x86_64', 41 | ] 42 | abiFilters = platforms.stream().map({ e -> 43 | platformMap.containsKey(e) ? platformMap[e] : e 44 | }).toArray() 45 | } 46 | } 47 | } 48 | } 49 | ``` 50 | The plugin will automatically download pre-build native libraries from [here](https://github.com/X-dea/Flutter_VPN/releases) if they haven't been downloaded. 51 | 52 | ### For iOS 53 | 54 | You need to open `Personal VPN` and `Network Extensions` capabilities in Xcode: see Project->Capabilities. 55 | 56 | VPN connection errors are handled in swift code, you need to use Xcode to see connection errors if there is any. 57 | -------------------------------------------------------------------------------- /ios/Classes/SwiftFlutterVpnPlugin.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2019 Jerry Wang, Jason C.H 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | import Flutter 16 | import UIKit 17 | 18 | @available(iOS 9.0, *) 19 | public class SwiftFlutterVpnPlugin: NSObject, FlutterPlugin { 20 | public static func register(with registrar: FlutterPluginRegistrar) { 21 | let channel = FlutterMethodChannel(name: "flutter_vpn", binaryMessenger: registrar.messenger()) 22 | let stateChannel = FlutterEventChannel(name: "flutter_vpn_states", binaryMessenger: registrar.messenger()) 23 | 24 | let instance = SwiftFlutterVpnPlugin() 25 | registrar.addMethodCallDelegate(instance, channel: channel) 26 | stateChannel.setStreamHandler((VPNStateHandler() as! FlutterStreamHandler & NSObjectProtocol)) 27 | 28 | channel.setMethodCallHandler { 29 | (call: FlutterMethodCall, result: FlutterResult) -> Void in 30 | if call.method == "connect" { 31 | let args = call.arguments! as! [NSString: NSString] 32 | VpnService.shared.connect( 33 | result: result, 34 | type: (args["Type"] as? String ?? "IKEv2"), 35 | server: args["Server"]! as String, 36 | username: args["Username"]! as String, 37 | password: args["Password"]! as String, 38 | secret: args["Secret"] as? String, 39 | description: args["Name"] as? String 40 | ) 41 | } else if call.method == "reconnect" { 42 | VpnService.shared.reconnect(result: result) 43 | } else if call.method == "disconnect" { 44 | VpnService.shared.disconnect(result: result) 45 | } else if call.method == "getCurrentState" { 46 | VpnService.shared.getState(result: result) 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'io.xdea.flutter_vpn' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.6.10' 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:7.1.3' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | // For downloading prebuilt libs 15 | classpath 'de.undercouch:gradle-download-task:4.1.1' 16 | } 17 | } 18 | 19 | apply plugin: 'de.undercouch.download' 20 | 21 | // Download prebuilt native libs. 22 | task downloadNativeLibArchive(type: Download) { 23 | src 'https://github.com/X-dea/Flutter_VPN/releases/download/v0.11.0/strongswan_libs_v5.9.5.zip' 24 | dest "$buildDir/native.zip" 25 | } 26 | 27 | task getPrebuiltNativeLibs(dependsOn: downloadNativeLibArchive, type: Copy) { 28 | from zipTree("$buildDir/native.zip") 29 | into "src/main/libs" 30 | } 31 | 32 | // Automatically download native libs before build when doesn't exist. 33 | tasks.whenTaskAdded { 34 | task -> 35 | if (task.name == "preBuild" && !(file('./src/main/libs').exists())) { 36 | preBuild.dependsOn getPrebuiltNativeLibs 37 | } 38 | } 39 | 40 | rootProject.allprojects { 41 | repositories { 42 | google() 43 | mavenCentral() 44 | } 45 | } 46 | 47 | apply plugin: 'com.android.library' 48 | apply plugin: 'kotlin-android' 49 | 50 | android { 51 | // Conditional for compatibility with AGP <4.2. 52 | if (project.android.hasProperty("namespace")) { 53 | namespace 'io.xdea.flutter_vpn' 54 | } 55 | 56 | compileSdkVersion 34 57 | 58 | compileOptions { 59 | sourceCompatibility JavaVersion.VERSION_1_8 60 | targetCompatibility JavaVersion.VERSION_1_8 61 | } 62 | 63 | kotlinOptions { 64 | jvmTarget = '1.8' 65 | } 66 | 67 | sourceSets.main { 68 | java.srcDirs += 'src/main/kotlin' 69 | jniLibs.srcDir 'src/main/libs' 70 | } 71 | 72 | defaultConfig { 73 | minSdkVersion 16 74 | consumerProguardFiles 'proguard-rules.pro' 75 | } 76 | } 77 | 78 | dependencies { 79 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 80 | } 81 | -------------------------------------------------------------------------------- /android/src/main/kotlin/io/xdea/flutter_vpn/VpnStateHandler.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2018-2020 Jason C.H 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | package io.xdea.flutter_vpn 16 | 17 | import android.os.Handler 18 | import android.os.Looper 19 | import io.flutter.plugin.common.EventChannel 20 | import org.strongswan.android.logic.VpnStateService 21 | 22 | /* 23 | States for Flutter VPN plugin. 24 | These states will be sent to event channel / getState. 25 | DISCONNECTED = 0; 26 | CONNECTING = 1; 27 | CONNECTED = 2; 28 | DISCONNECTING = 3; 29 | ERROR = 4; 30 | 31 | Charon error states. 32 | Details of error when state shows GENERIC_ERROR. 33 | NO_ERROR = 0 34 | AUTH_FAILED = 1 35 | PEER_AUTH_FAILED = 2 36 | LOOKUP_FAILED = 3 37 | UNREACHABLE = 4 38 | GENERIC_ERROR = 5 39 | PASSWORD_MISSING = 6 40 | CERTIFICATE_UNAVAILABLE = 7 41 | */ 42 | 43 | object VpnStateHandler : EventChannel.StreamHandler, VpnStateService.VpnStateListener { 44 | // Handle event in main thread. 45 | private val handler = Handler(Looper.getMainLooper()) 46 | // The charon VPN service will update state through the sink if not `null`. 47 | private var eventSink: EventChannel.EventSink? = null 48 | // Will be registered when service bound successfully. 49 | var vpnStateService: VpnStateService? = null 50 | 51 | override fun stateChanged() { 52 | if (vpnStateService?.errorState != VpnStateService.ErrorState.NO_ERROR) 53 | handler.post { eventSink?.success(4) } 54 | else 55 | eventSink?.success(vpnStateService?.state?.ordinal) 56 | } 57 | 58 | override fun onListen(p0: Any?, sink: EventChannel.EventSink) { 59 | eventSink = sink 60 | } 61 | 62 | override fun onCancel(p0: Any?) { 63 | eventSink = null 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | VPN connection state 21 | Provides information about the VPN connection state and serves as permanent notification to keep the VPN service running in the background. 22 | 23 | 24 | Connecting… 25 | Connected 26 | Disconnecting… 27 | No active VPN 28 | 29 | 30 | Server address lookup failed 31 | Server is unreachable 32 | Verifying server authentication failed 33 | User authentication failed 34 | Security assessment failed 35 | Unspecified failure while connecting 36 | Password unavailable 37 | Client certificate unavailable 38 | 39 | Retry in %1$d second 40 | Retry in %1$d seconds 41 | 42 | 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.13.0 2 | 3 | - Fix crash on Android under SDK34 4 | - Support IPSec on iOS 5 | 6 | ## 0.12.0 7 | 8 | - Upgrade to StrongSwan 5.9.5. 9 | - Fix crash on Android release build. 10 | - Migrate to federated plugin. 11 | 12 | ## 0.10.0 13 | - Upgrade to StrongSwan 5.9.0. 14 | - Fix typo in event channel. 15 | - Expose port and name settings (Android). 16 | 17 | ## 0.9.0 18 | - Upgrade to StrongSwan 5.9.0. 19 | - Add `prepared` for checking vpn preparation on android. 20 | 21 | ## 0.8.0 22 | - Fix crash on launch caused by abiFilters. (#45) 23 | 24 | **Breaking Changes** 25 | - Support Android embedding v2 (v1 is nolonger supported). 26 | - Please update abiFilters according to the description in README. 27 | 28 | ## 0.7.0 29 | - Add MTU for Android. 30 | - Fix service unbinding. (#27) 31 | 32 | ## 0.6.0 33 | - Update to StrongSwan 5.8.1. 34 | - Use original notification from StrongSwan frontend. 35 | - Automatically retry when a error occured. 36 | 37 | **BreakingChange** 38 | 39 | - In order to compatible with original `VpnStateService`, `CharonVpnState` has been changed to `CharonErrorState` that shows detail kind of error when a generic error is received. 40 | 41 | ## 0.5.0 42 | - Fix (#15) event handler for android (Flutter 1.6+). 43 | 44 | ## 0.4.0 45 | - Fix state error if disconnect while connecting. 46 | - Add iOS state handler. 47 | 48 | ## 0.3.0 49 | - Add `getVpnState` for iOS. 50 | 51 | ## 0.2.0 52 | - Add `getVpnState` and `getCharonState` for Android. 53 | 54 | **Breaking Change** 55 | 56 | - Old `FlutterVpnState` has been renamed to `CharonVpnState` which is for Android only. New `FlutterVpnState` is designed for both Android and iOS platform. 57 | 58 | ## 0.1.0 59 | - Support `arm64-v8a` for android. Please follow `README` to configure abiFilter for NDK. 60 | 61 | **Breaking Change** 62 | 63 | - Migrate to AndroidX 64 | 65 | > Migrate from the deprecated original Android Support Library to AndroidX. This shouldn't result in any functional changes, but it requires any Android apps using this plugin to also migrate if they're using the original support library. 66 | > Follow [Official documents](https://developer.android.com/jetpack/androidx/migrate) to migrate. 67 | 68 | ## 0.0.4 69 | - Add iOS support without status broadcast. 70 | 71 | ## 0.0.3 72 | - Add `onStateChanged` to receive state changes from charon. 73 | 74 | ## 0.0.2 (Deprecated) 75 | - Implemented simplest IkeV2-eap VPN service. 76 | - Automatically download native libs before building. 77 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/utils/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2020 Tobias Brunner 3 | * HSR Hochschule fuer Technik Rapperswil 4 | * 5 | * This program is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License as published by the 7 | * Free Software Foundation; either version 2 of the License, or (at your 8 | * option) any later version. See . 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | */ 15 | 16 | package org.strongswan.android.utils; 17 | 18 | public final class Constants 19 | { 20 | /** 21 | * Intent action used to notify about changes to the VPN profiles 22 | */ 23 | public static final String VPN_PROFILES_CHANGED = "org.strongswan.android.VPN_PROFILES_CHANGED"; 24 | 25 | /** 26 | * Used in the intent above to notify about edits or inserts of a VPN profile (long) 27 | */ 28 | public static final String VPN_PROFILES_SINGLE = "org.strongswan.android.VPN_PROFILES_SINGLE"; 29 | 30 | /** 31 | * Used in the intent above to notify about the deletion of multiple VPN profiles (array of longs) 32 | */ 33 | public static final String VPN_PROFILES_MULTIPLE = "org.strongswan.android.VPN_PROFILES_MULTIPLE"; 34 | 35 | /** 36 | * Limits for MTU 37 | */ 38 | public static final int MTU_MAX = 1500; 39 | public static final int MTU_MIN = 1280; 40 | 41 | /** 42 | * Limits for NAT-T keepalive 43 | */ 44 | public static final int NAT_KEEPALIVE_MAX = 120; 45 | public static final int NAT_KEEPALIVE_MIN = 10; 46 | 47 | /** 48 | * Preference key for default VPN profile 49 | */ 50 | public static final String PREF_DEFAULT_VPN_PROFILE = "pref_default_vpn_profile"; 51 | 52 | /** 53 | * Value used to signify that the most recently used profile should be used as default 54 | */ 55 | public static final String PREF_DEFAULT_VPN_PROFILE_MRU = "pref_default_vpn_profile_mru"; 56 | 57 | /** 58 | * Preference key to store the most recently used VPN profile 59 | */ 60 | public static final String PREF_MRU_VPN_PROFILE = "pref_mru_vpn_profile"; 61 | 62 | /** 63 | * Preference key to store whether the user permanently dismissed our warning to add the app to the power whitelist 64 | */ 65 | public static final String PREF_IGNORE_POWER_WHITELIST = "pref_ignore_power_whitelist"; 66 | } 67 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/utils/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014-2019 Tobias Brunner 3 | * HSR Hochschule fuer Technik Rapperswil 4 | * 5 | * This program is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License as published by the 7 | * Free Software Foundation; either version 2 of the License, or (at your 8 | * option) any later version. See . 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | */ 15 | 16 | package org.strongswan.android.utils; 17 | 18 | 19 | import java.net.InetAddress; 20 | import java.net.UnknownHostException; 21 | 22 | public class Utils 23 | { 24 | static final char[] HEXDIGITS = "0123456789abcdef".toCharArray(); 25 | 26 | /** 27 | * Converts the given byte array to a hexadecimal string encoding. 28 | * 29 | * @param bytes byte array to convert 30 | * @return hex string 31 | */ 32 | public static String bytesToHex(byte[] bytes) 33 | { 34 | char[] hex = new char[bytes.length * 2]; 35 | for (int i = 0; i < bytes.length; i++) 36 | { 37 | int value = bytes[i]; 38 | hex[i*2] = HEXDIGITS[(value & 0xf0) >> 4]; 39 | hex[i*2+1] = HEXDIGITS[ value & 0x0f]; 40 | } 41 | return new String(hex); 42 | } 43 | 44 | /** 45 | * Validate the given proposal string 46 | * 47 | * @param ike true for IKE, false for ESP 48 | * @param proposal proposal string 49 | * @return true if valid 50 | */ 51 | public native static boolean isProposalValid(boolean ike, String proposal); 52 | 53 | /** 54 | * Parse an IP address without doing a name lookup 55 | * 56 | * @param address IP address string 57 | * @return address bytes if valid 58 | */ 59 | private native static byte[] parseInetAddressBytes(String address); 60 | 61 | /** 62 | * Parse an IP address without doing a name lookup (as compared to InetAddress.fromName()) 63 | * 64 | * @param address IP address string 65 | * @return address if valid 66 | * @throws UnknownHostException if address is invalid 67 | */ 68 | public static InetAddress parseInetAddress(String address) throws UnknownHostException 69 | { 70 | byte[] bytes = parseInetAddressBytes(address); 71 | if (bytes == null) 72 | { 73 | throw new UnknownHostException(); 74 | } 75 | return InetAddress.getByAddress(bytes); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/attributes/PortFilterAttribute.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * Copyright (C) 2012 Christoph Buehler 4 | * Copyright (C) 2012 Patrick Loetscher 5 | * HSR Hochschule fuer Technik Rapperswil 6 | * 7 | * This program is free software; you can redistribute it and/or modify it 8 | * under the terms of the GNU General Public License as published by the 9 | * Free Software Foundation; either version 2 of the License, or (at your 10 | * option) any later version. See . 11 | * 12 | * This program is distributed in the hope that it will be useful, but 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 14 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 | * for more details. 16 | */ 17 | 18 | package org.strongswan.android.logic.imc.attributes; 19 | 20 | import java.util.LinkedList; 21 | 22 | import org.strongswan.android.logic.imc.collectors.Protocol; 23 | import org.strongswan.android.utils.BufferedByteWriter; 24 | 25 | import android.util.Pair; 26 | 27 | /** 28 | * PA-TNC Port Filter attribute (see section 4.2.6 of RFC 5792) 29 | * 30 | * 1 2 3 31 | * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 32 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 33 | * | Reserved |B| Protocol | Port Number | 34 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 35 | * | Reserved |B| Protocol | Port Number | 36 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 37 | */ 38 | public class PortFilterAttribute implements Attribute 39 | { 40 | private final LinkedList> mPorts = new LinkedList>(); 41 | 42 | /** 43 | * Add an open port with the given protocol and port number 44 | * @param protocol transport protocol 45 | * @param port port number 46 | */ 47 | public void addPort(Protocol protocol, short port) 48 | { 49 | mPorts.add(new Pair(protocol, port)); 50 | } 51 | 52 | @Override 53 | public byte[] getEncoding() 54 | { 55 | BufferedByteWriter writer = new BufferedByteWriter(); 56 | for (Pair port : mPorts) 57 | { 58 | /* we report open ports, so the BLOCKED flag is not set */ 59 | writer.put((byte)0); 60 | writer.put(port.first.getValue()); 61 | writer.put16(port.second); 62 | } 63 | return writer.toByteArray(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/attributes/StringVersionAttribute.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * Copyright (C) 2012 Christoph Buehler 4 | * Copyright (C) 2012 Patrick Loetscher 5 | * HSR Hochschule fuer Technik Rapperswil 6 | * 7 | * This program is free software; you can redistribute it and/or modify it 8 | * under the terms of the GNU General Public License as published by the 9 | * Free Software Foundation; either version 2 of the License, or (at your 10 | * option) any later version. See . 11 | * 12 | * This program is distributed in the hope that it will be useful, but 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 14 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 | * for more details. 16 | */ 17 | 18 | package org.strongswan.android.logic.imc.attributes; 19 | 20 | import org.strongswan.android.utils.BufferedByteWriter; 21 | 22 | /** 23 | * PA-TNC String Version attribute (see section 4.2.4 of RFC 5792) 24 | * 25 | * 1 2 3 26 | * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 27 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 28 | * | Version Len | Product Version Number (Variable Length) | 29 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 30 | * | Build Num Len | Internal Build Number (Variable Length) | 31 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 32 | * | Config. Len | Configuration Version Number (Variable Length)| 33 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 34 | */ 35 | public class StringVersionAttribute implements Attribute 36 | { 37 | private String mVersionNumber; 38 | private String mBuildNumber; 39 | 40 | /** 41 | * Set the product version number 42 | * @param version version number 43 | */ 44 | public void setProductVersionNumber(String version) 45 | { 46 | this.mVersionNumber = version; 47 | } 48 | 49 | /** 50 | * Set the internal build number 51 | * @param build build number 52 | */ 53 | public void setInternalBuildNumber(String build) 54 | { 55 | this.mBuildNumber = build; 56 | } 57 | 58 | @Override 59 | public byte[] getEncoding() 60 | { 61 | BufferedByteWriter writer = new BufferedByteWriter(); 62 | writer.putLen8(mVersionNumber.getBytes()); 63 | writer.putLen8(mBuildNumber.getBytes()); 64 | /* we don't provide a configuration number */ 65 | writer.put((byte)0); 66 | return writer.toByteArray(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/attributes/InstalledPackagesAttribute.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * Copyright (C) 2012 Christoph Buehler 4 | * Copyright (C) 2012 Patrick Loetscher 5 | * HSR Hochschule fuer Technik Rapperswil 6 | * 7 | * This program is free software; you can redistribute it and/or modify it 8 | * under the terms of the GNU General Public License as published by the 9 | * Free Software Foundation; either version 2 of the License, or (at your 10 | * option) any later version. See . 11 | * 12 | * This program is distributed in the hope that it will be useful, but 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 14 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 | * for more details. 16 | */ 17 | 18 | package org.strongswan.android.logic.imc.attributes; 19 | 20 | import java.util.LinkedList; 21 | 22 | import org.strongswan.android.utils.BufferedByteWriter; 23 | 24 | import android.util.Pair; 25 | 26 | /** 27 | * PA-TNC Installed Packages attribute (see section 4.2.7 of RFC 5792) 28 | * 29 | * 1 2 3 30 | * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 31 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 32 | * | Reserved | Package Count | 33 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 34 | * | Pkg Name Len | Package Name (Variable Length) | 35 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 36 | * | Version Len | Package Version Number (Variable Length) | 37 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 38 | */ 39 | public class InstalledPackagesAttribute implements Attribute 40 | { 41 | private final short RESERVED = 0; 42 | private final LinkedList> mPackages = new LinkedList>(); 43 | 44 | /** 45 | * Add an installed package to this attribute. 46 | * @param name name of the package 47 | * @param version version number of the package 48 | */ 49 | public void addPackage(String name, String version) 50 | { 51 | mPackages.add(new Pair(name, version)); 52 | } 53 | 54 | @Override 55 | public byte[] getEncoding() 56 | { 57 | BufferedByteWriter writer = new BufferedByteWriter(); 58 | writer.put16(RESERVED); 59 | writer.put16((short)mPackages.size()); 60 | for (Pair pair : mPackages) 61 | { 62 | writer.putLen8(pair.first.getBytes()); 63 | writer.putLen8(pair.second.getBytes()); 64 | } 65 | return writer.toByteArray(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/collectors/PortFilterCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * Copyright (C) 2012 Christoph Buehler 4 | * Copyright (C) 2012 Patrick Loetscher 5 | * HSR Hochschule fuer Technik Rapperswil 6 | * 7 | * This program is free software; you can redistribute it and/or modify it 8 | * under the terms of the GNU General Public License as published by the 9 | * Free Software Foundation; either version 2 of the License, or (at your 10 | * option) any later version. See . 11 | * 12 | * This program is distributed in the hope that it will be useful, but 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 14 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 | * for more details. 16 | */ 17 | 18 | package org.strongswan.android.logic.imc.collectors; 19 | 20 | import java.io.BufferedReader; 21 | import java.io.IOException; 22 | import java.io.InputStreamReader; 23 | import java.util.regex.Matcher; 24 | import java.util.regex.Pattern; 25 | 26 | import org.strongswan.android.logic.imc.attributes.Attribute; 27 | import org.strongswan.android.logic.imc.attributes.PortFilterAttribute; 28 | 29 | public class PortFilterCollector implements Collector 30 | { 31 | private static Pattern LISTEN = Pattern.compile("\\bLISTEN\\b"); 32 | private static Pattern PROTOCOL = Pattern.compile("\\b(tcp|udp)6?\\b"); 33 | private static Pattern PORT = Pattern.compile("[:]{1,3}(\\d{1,5})\\b(?!\\.)"); 34 | 35 | @Override 36 | public Attribute getMeasurement() 37 | { 38 | PortFilterAttribute attribute = null; 39 | try 40 | { 41 | Process netstat = Runtime.getRuntime().exec("netstat -n"); 42 | try 43 | { 44 | BufferedReader reader = new BufferedReader(new InputStreamReader(netstat.getInputStream())); 45 | String line; 46 | attribute = new PortFilterAttribute(); 47 | while ((line = reader.readLine()) != null) 48 | { 49 | if (!LISTEN.matcher(line).find()) 50 | { 51 | continue; 52 | } 53 | Matcher protocolMatcher = PROTOCOL.matcher(line); 54 | Matcher portMatcher = PORT.matcher(line); 55 | if (protocolMatcher.find() && portMatcher.find()) 56 | { 57 | Protocol protocol = Protocol.fromName(protocolMatcher.group()); 58 | if (protocol == null) 59 | { 60 | continue; 61 | } 62 | int port = Integer.parseInt(portMatcher.group(1)); 63 | attribute.addPort(protocol, (short)port); 64 | } 65 | } 66 | } 67 | finally 68 | { 69 | netstat.destroy(); 70 | } 71 | } 72 | catch (IOException e) 73 | { 74 | e.printStackTrace(); 75 | } 76 | return attribute; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_vpn 2 | description: Plugin for developers to access VPN service in their flutter app. 3 | version: 0.13.0 4 | homepage: https://github.com/X-dea/flutter_vpn 5 | 6 | environment: 7 | sdk: ">=2.12.0 <4.0.0" 8 | flutter: ">=2.5.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | plugin_platform_interface: ^2.0.2 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | flutter_lints: ^2.0.0 19 | 20 | # For information on the generic Dart part of this file, see the 21 | # following page: https://dart.dev/tools/pub/pubspec 22 | 23 | # The following section is specific to Flutter packages. 24 | flutter: 25 | # This section identifies this Flutter project as a plugin project. 26 | # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) 27 | # which should be registered in the plugin registry. This is required for 28 | # using method channels. 29 | # The Android 'package' specifies package in which the registered class is. 30 | # This is required for using method channels on Android. 31 | # The 'ffiPlugin' specifies that native code should be built and bundled. 32 | # This is required for using `dart:ffi`. 33 | # All these are used by the tooling to maintain consistency when 34 | # adding or updating assets for this project. 35 | plugin: 36 | platforms: 37 | android: 38 | package: io.xdea.flutter_vpn 39 | pluginClass: FlutterVpnPlugin 40 | ios: 41 | pluginClass: FlutterVpnPlugin 42 | 43 | # To add assets to your plugin package, add an assets section, like this: 44 | # assets: 45 | # - images/a_dot_burr.jpeg 46 | # - images/a_dot_ham.jpeg 47 | # 48 | # For details regarding assets in packages, see 49 | # https://flutter.dev/assets-and-images/#from-packages 50 | # 51 | # An image asset can refer to one or more resolution-specific "variants", see 52 | # https://flutter.dev/assets-and-images/#resolution-aware 53 | 54 | # To add custom fonts to your plugin package, add a fonts section here, 55 | # in this "flutter" section. Each entry in this list should have a 56 | # "family" key with the font family name, and a "fonts" key with a 57 | # list giving the asset and other descriptors for the font. For 58 | # example: 59 | # fonts: 60 | # - family: Schyler 61 | # fonts: 62 | # - asset: fonts/Schyler-Regular.ttf 63 | # - asset: fonts/Schyler-Italic.ttf 64 | # style: italic 65 | # - family: Trajan Pro 66 | # fonts: 67 | # - asset: fonts/TrajanPro.ttf 68 | # - asset: fonts/TrajanPro_Bold.ttf 69 | # weight: 700 70 | # 71 | # For details regarding fonts in packages, see 72 | # https://flutter.dev/custom-fonts/#from-packages 73 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/data/VpnType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2014 Tobias Brunner 3 | * HSR Hochschule fuer Technik Rapperswil 4 | * 5 | * This program is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License as published by the 7 | * Free Software Foundation; either version 2 of the License, or (at your 8 | * option) any later version. See . 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | */ 15 | 16 | package org.strongswan.android.data; 17 | 18 | import java.util.EnumSet; 19 | 20 | public enum VpnType 21 | { 22 | /* the order here must match the items in R.array.vpn_types */ 23 | IKEV2_EAP("ikev2-eap", EnumSet.of(VpnTypeFeature.USER_PASS)), 24 | IKEV2_CERT("ikev2-cert", EnumSet.of(VpnTypeFeature.CERTIFICATE)), 25 | IKEV2_CERT_EAP("ikev2-cert-eap", EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.CERTIFICATE)), 26 | IKEV2_EAP_TLS("ikev2-eap-tls", EnumSet.of(VpnTypeFeature.CERTIFICATE)), 27 | IKEV2_BYOD_EAP("ikev2-byod-eap", EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.BYOD)); 28 | 29 | /** 30 | * Features of a VPN type. 31 | */ 32 | public enum VpnTypeFeature 33 | { 34 | /** client certificate is required */ 35 | CERTIFICATE, 36 | /** username and password are required */ 37 | USER_PASS, 38 | /** enable BYOD features */ 39 | BYOD; 40 | } 41 | 42 | private String mIdentifier; 43 | private EnumSet mFeatures; 44 | 45 | /** 46 | * Enum which provides additional information about the supported VPN types. 47 | * 48 | * @param id identifier used to store and transmit this specific type 49 | * @param features of the given VPN type 50 | * @param certificate true if a client certificate is required 51 | */ 52 | VpnType(String id, EnumSet features) 53 | { 54 | mIdentifier = id; 55 | mFeatures = features; 56 | } 57 | 58 | /** 59 | * The identifier used to store this value in the database 60 | * @return identifier 61 | */ 62 | public String getIdentifier() 63 | { 64 | return mIdentifier; 65 | } 66 | 67 | /** 68 | * Checks whether a feature is supported/required by this type of VPN. 69 | * 70 | * @return true if the feature is supported/required 71 | */ 72 | public boolean has(VpnTypeFeature feature) 73 | { 74 | return mFeatures.contains(feature); 75 | } 76 | 77 | /** 78 | * Get the enum entry with the given identifier. 79 | * 80 | * @param identifier get the enum entry with this identifier 81 | * @return the enum entry, or the default if not found 82 | */ 83 | public static VpnType fromIdentifier(String identifier) 84 | { 85 | for (VpnType type : VpnType.values()) 86 | { 87 | if (identifier.equals(type.mIdentifier)) 88 | { 89 | return type; 90 | } 91 | } 92 | return VpnType.IKEV2_EAP; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /ios/Classes/KeychainService.swift: -------------------------------------------------------------------------------- 1 | 2 | // Identifiers 3 | let serviceIdentifier = "MySerivice" 4 | let userAccount = "authenticatedUser" 5 | let accessGroup = "MySerivice" 6 | 7 | // Arguments for the keychain queries 8 | var kSecAttrAccessGroupSwift = NSString(format: kSecClass) 9 | 10 | let kSecClassValue: String = kSecClass as String 11 | let kSecAttrAccountValue: String = kSecAttrAccount as String 12 | let kSecValueDataValue: String = kSecValueData as String 13 | let kSecClassGenericPasswordValue: String = kSecClassGenericPassword as String 14 | let kSecAttrServiceValue: String = kSecAttrService as String 15 | let kSecMatchLimitValue: String = kSecMatchLimit as String 16 | let kSecReturnDataValue: String = kSecReturnData as String 17 | let kSecMatchLimitOneValue: String = kSecMatchLimitOne as String 18 | let kSecAttrGenericValue: String = kSecAttrGeneric as String 19 | let kSecAttrAccessibleValue: String = kSecAttrAccessible as String 20 | let kSecReturnPersistentRefValue: String = kSecReturnPersistentRef as String 21 | 22 | 23 | class KeychainService: NSObject { 24 | var serviceName: String 25 | 26 | init(serviceName: String = "flutter_vpn") { 27 | self.serviceName = serviceName 28 | } 29 | 30 | func save(key: String, value: String) { 31 | let keyData: Data = key.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue), allowLossyConversion: false)! 32 | let valueData: Data = value.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue), allowLossyConversion: false)! 33 | 34 | let keychainQuery = NSMutableDictionary() 35 | keychainQuery[kSecClassValue] = kSecClassGenericPasswordValue 36 | keychainQuery[kSecAttrGenericValue] = keyData 37 | keychainQuery[kSecAttrAccountValue] = keyData 38 | keychainQuery[kSecAttrServiceValue] = self.serviceName 39 | keychainQuery[kSecAttrAccessibleValue] = kSecAttrAccessibleAfterFirstUnlock 40 | keychainQuery[kSecValueDataValue] = valueData 41 | 42 | // Delete any existing items 43 | SecItemDelete(keychainQuery as CFDictionary) 44 | SecItemAdd(keychainQuery as CFDictionary, nil) 45 | } 46 | 47 | func load(key: String) -> Data? { 48 | let keyData: Data = key.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue), allowLossyConversion: false)! 49 | let keychainQuery = NSMutableDictionary() 50 | keychainQuery[kSecClassValue] = kSecClassGenericPasswordValue 51 | keychainQuery[kSecAttrGenericValue] = keyData 52 | keychainQuery[kSecAttrAccountValue] = keyData 53 | keychainQuery[kSecAttrServiceValue] = self.serviceName 54 | keychainQuery[kSecAttrAccessibleValue] = kSecAttrAccessibleAfterFirstUnlock 55 | keychainQuery[kSecMatchLimitValue] = kSecMatchLimitOne 56 | keychainQuery[kSecReturnPersistentRefValue] = kCFBooleanTrue 57 | 58 | var result: AnyObject? 59 | let status = SecItemCopyMatching(keychainQuery as CFDictionary, &result) 60 | 61 | return status == noErr ? result as? Data : nil 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion flutter.compileSdkVersion 30 | ndkVersion flutter.ndkVersion 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | 41 | sourceSets { 42 | main.java.srcDirs += 'src/main/kotlin' 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "io.xdea.flutter_vpn_example" 48 | // You can update the following values to match your application needs. 49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. 50 | minSdkVersion flutter.minSdkVersion 51 | targetSdkVersion flutter.targetSdkVersion 52 | versionCode flutterVersionCode.toInteger() 53 | versionName flutterVersionName 54 | } 55 | 56 | buildTypes { 57 | release { 58 | // TODO: Add your own signing config for the release build. 59 | // Signing with the debug keys for now, so `flutter run --release` works. 60 | signingConfig signingConfigs.debug 61 | 62 | ndk { 63 | if (!project.hasProperty('target-platform')) { 64 | abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64' 65 | } else { 66 | def platforms = project.property('target-platform').split(',') 67 | def platformMap = [ 68 | 'android-arm' : 'armeabi-v7a', 69 | 'android-arm64': 'arm64-v8a', 70 | 'android-x86' : 'x86', 71 | 'android-x64' : 'x86_64', 72 | ] 73 | abiFilters = platforms.stream().map({ e -> 74 | platformMap.containsKey(e) ? platformMap[e] : e 75 | }).toArray() 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | flutter { 83 | source '../..' 84 | } 85 | 86 | dependencies { 87 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 88 | } 89 | -------------------------------------------------------------------------------- /lib/flutter_vpn.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2018-2022 Jason C.H 2 | /// 3 | /// This library is free software; you can redistribute it and/or 4 | /// modify it under the terms of the GNU Lesser General Public 5 | /// License as published by the Free Software Foundation; either 6 | /// version 2.1 of the License, or (at your option) any later version. 7 | /// 8 | /// This library is distributed in the hope that it will be useful, 9 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | /// Lesser General Public License for more details. 12 | import 'flutter_vpn_platform_interface.dart'; 13 | import 'state.dart'; 14 | 15 | class FlutterVpn { 16 | /// Receive state change from VPN service. 17 | /// 18 | /// Can only be listened once. If have more than one subscription, only the 19 | /// last subscription can receive events. 20 | static Stream get onStateChanged => FlutterVpnPlatform.instance.onStateChanged; 21 | 22 | /// Get current state. 23 | static Future get currentState => FlutterVpnPlatform.instance.currentState; 24 | 25 | /// Get current error state from `VpnStateService`. (Android only) 26 | /// When [FlutterVpnState.error] is received, details of error can be 27 | /// inspected by [CharonErrorState]. Returns [null] on non-android platform. 28 | static Future get charonErrorState => FlutterVpnPlatform.instance.charonErrorState; 29 | 30 | /// Prepare for vpn connection. (Android only) 31 | /// 32 | /// For first connection it will show a dialog to ask for permission. 33 | /// When your connection was interrupted by another VPN connection, 34 | /// you should prepare again before reconnect. 35 | static Future prepare() => FlutterVpnPlatform.instance.prepare(); 36 | 37 | /// Check if vpn connection has been prepared. (Android only) 38 | static Future get prepared => FlutterVpnPlatform.instance.prepared; 39 | 40 | /// Disconnect and stop VPN service. 41 | static Future disconnect() => FlutterVpnPlatform.instance.disconnect(); 42 | 43 | /// Connect to VPN. (IKEv2-EAP) 44 | /// 45 | /// This will create a background VPN service. 46 | /// MTU is only available on android. 47 | static Future connectIkev2EAP({ 48 | required String server, 49 | required String username, 50 | required String password, 51 | String? name, 52 | int? mtu, 53 | int? port, 54 | }) => 55 | FlutterVpnPlatform.instance.connectIkev2EAP( 56 | server: server, 57 | username: username, 58 | password: password, 59 | name: name, 60 | mtu: mtu, 61 | port: port, 62 | ); 63 | 64 | /// Connect to VPN. (IPSec) 65 | /// 66 | /// This will create a background VPN service. 67 | /// Android implementation is not available. 68 | static Future connectIPSec({ 69 | required String server, 70 | required String username, 71 | required String password, 72 | required String secret, 73 | String? name, 74 | int? mtu, 75 | int? port, 76 | }) async => 77 | FlutterVpnPlatform.instance.connectIPSec( 78 | server: server, 79 | username: username, 80 | password: password, 81 | secret: secret, 82 | name: name, 83 | mtu: mtu, 84 | port: port, 85 | ); 86 | } 87 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/attributes/SettingsAttribute.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * Copyright (C) 2012 Christoph Buehler 4 | * Copyright (C) 2012 Patrick Loetscher 5 | * HSR Hochschule fuer Technik Rapperswil 6 | * 7 | * This program is free software; you can redistribute it and/or modify it 8 | * under the terms of the GNU General Public License as published by the 9 | * Free Software Foundation; either version 2 of the License, or (at your 10 | * option) any later version. See . 11 | * 12 | * This program is distributed in the hope that it will be useful, but 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 14 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 | * for more details. 16 | */ 17 | 18 | package org.strongswan.android.logic.imc.attributes; 19 | 20 | import java.util.LinkedList; 21 | 22 | import org.strongswan.android.utils.BufferedByteWriter; 23 | 24 | import android.util.Pair; 25 | 26 | /** 27 | * ITA Settings attribute 28 | * 29 | * 1 2 3 30 | * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 31 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 32 | * | Settings Count | 33 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 34 | * | Name Length | Name (Variable Length) ~ 35 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 36 | * ~ Name (Variable Length) ~ 37 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 38 | * | Value Length | Value (Variable Length) ~ 39 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 40 | * ~ Value (Variable Length) ~ 41 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 42 | * | Name Length | Name (Variable Length) ~ 43 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 44 | * ~ Name (Variable Length) ~ 45 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 46 | * | Value Length | Value (Variable Length) ~ 47 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 48 | * ~ Value (Variable Length) ~ 49 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 50 | * ........................... 51 | */ 52 | public class SettingsAttribute implements Attribute 53 | { 54 | private final LinkedList> mSettings = new LinkedList>(); 55 | 56 | /** 57 | * Add a setting to this attribute. 58 | * @param name name of the setting 59 | * @param value value of the setting 60 | */ 61 | public void addSetting(String name, String value) 62 | { 63 | mSettings.add(new Pair(name, value)); 64 | } 65 | 66 | @Override 67 | public byte[] getEncoding() 68 | { 69 | BufferedByteWriter writer = new BufferedByteWriter(); 70 | writer.put32(mSettings.size()); 71 | for (Pair pair : mSettings) 72 | { 73 | writer.putLen16(pair.first.getBytes()); 74 | writer.putLen16(pair.second.getBytes()); 75 | } 76 | return writer.toByteArray(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/attributes/AttributeType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * Copyright (C) 2012 Christoph Buehler 4 | * Copyright (C) 2012 Patrick Loetscher 5 | * HSR Hochschule fuer Technik Rapperswil 6 | * 7 | * This program is free software; you can redistribute it and/or modify it 8 | * under the terms of the GNU General Public License as published by the 9 | * Free Software Foundation; either version 2 of the License, or (at your 10 | * option) any later version. See . 11 | * 12 | * This program is distributed in the hope that it will be useful, but 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 14 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 | * for more details. 16 | */ 17 | 18 | package org.strongswan.android.logic.imc.attributes; 19 | 20 | public enum AttributeType 21 | { 22 | /* IETF standard PA-TNC attribute types defined by RFC 5792 */ 23 | IETF_TESTING(PrivateEnterpriseNumber.IETF, 0), 24 | IETF_ATTRIBUTE_REQUEST(PrivateEnterpriseNumber.IETF, 1), 25 | IETF_PRODUCT_INFORMATION(PrivateEnterpriseNumber.IETF, 2), 26 | IETF_NUMERIC_VERSION(PrivateEnterpriseNumber.IETF, 3), 27 | IETF_STRING_VERSION(PrivateEnterpriseNumber.IETF, 4), 28 | IETF_OPERATIONAL_STATUS(PrivateEnterpriseNumber.IETF, 5), 29 | IETF_PORT_FILTER(PrivateEnterpriseNumber.IETF, 6), 30 | IETF_INSTALLED_PACKAGES(PrivateEnterpriseNumber.IETF, 7), 31 | IETF_PA_TNC_ERROR(PrivateEnterpriseNumber.IETF, 8), 32 | IETF_ASSESSMENT_RESULT(PrivateEnterpriseNumber.IETF, 9), 33 | IETF_REMEDIATION_INSTRUCTIONS(PrivateEnterpriseNumber.IETF, 10), 34 | IETF_FORWARDING_ENABLED(PrivateEnterpriseNumber.IETF, 11), 35 | IETF_FACTORY_DEFAULT_PWD_ENABLED(PrivateEnterpriseNumber.IETF, 12), 36 | IETF_RESERVED(PrivateEnterpriseNumber.IETF, 0xffffffff), 37 | /* ITA attributes */ 38 | ITA_SETTINGS(PrivateEnterpriseNumber.ITA, 4), 39 | ITA_DEVICE_ID(PrivateEnterpriseNumber.ITA, 8); 40 | 41 | private PrivateEnterpriseNumber mVendor; 42 | private int mType; 43 | 44 | /** 45 | * Enum type for vendor specific attributes (defined in their namespace) 46 | * 47 | * @param vendor private enterprise number of vendor 48 | * @param type vendor specific attribute type 49 | */ 50 | private AttributeType(PrivateEnterpriseNumber vendor, int type) 51 | { 52 | mVendor = vendor; 53 | mType = type; 54 | } 55 | 56 | /** 57 | * Get private enterprise number of vendor 58 | * 59 | * @return PEN 60 | */ 61 | public PrivateEnterpriseNumber getVendor() 62 | { 63 | return mVendor; 64 | } 65 | 66 | /** 67 | * Get vendor specific type 68 | * 69 | * @return type 70 | */ 71 | public int getType() 72 | { 73 | return mType; 74 | } 75 | 76 | /** 77 | * Get the enum entry from the given numeric values, if defined 78 | * 79 | * @param vendor vendor id 80 | * @param type vendor specific type 81 | * @return enum entry or null 82 | */ 83 | public static AttributeType fromValues(int vendor, int type) 84 | { 85 | PrivateEnterpriseNumber pen = PrivateEnterpriseNumber.fromValue(vendor); 86 | 87 | if (pen == null) 88 | { 89 | return null; 90 | } 91 | for (AttributeType attr : AttributeType.values()) 92 | { 93 | if (attr.mVendor == pen && attr.mType == type) 94 | { 95 | return attr; 96 | } 97 | } 98 | return null; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_vpn_example 2 | description: Demonstrates how to use the flutter_vpn plugin. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | environment: 9 | sdk: ">=2.12.0 <3.0.0" 10 | 11 | # Dependencies specify other packages that your package needs in order to work. 12 | # To automatically upgrade your package dependencies to the latest versions 13 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 14 | # dependencies can be manually updated by changing the version numbers below to 15 | # the latest version available on pub.dev. To see which dependencies have newer 16 | # versions available, run `flutter pub outdated`. 17 | dependencies: 18 | flutter: 19 | sdk: flutter 20 | 21 | flutter_vpn: 22 | # When depending on this package from a real application you should use: 23 | # flutter_vpn: ^x.y.z 24 | # See https://dart.dev/tools/pub/dependencies#version-constraints 25 | # The example app is bundled with the plugin so we use a path dependency on 26 | # the parent directory to use the current plugin's version. 27 | path: ../ 28 | 29 | # The following adds the Cupertino Icons font to your application. 30 | # Use with the CupertinoIcons class for iOS style icons. 31 | cupertino_icons: ^1.0.2 32 | 33 | dev_dependencies: 34 | flutter_test: 35 | sdk: flutter 36 | 37 | # The "flutter_lints" package below contains a set of recommended lints to 38 | # encourage good coding practices. The lint set provided by the package is 39 | # activated in the `analysis_options.yaml` file located at the root of your 40 | # package. See that file for information about deactivating specific lint 41 | # rules and activating additional ones. 42 | flutter_lints: ^2.0.0 43 | 44 | # For information on the generic Dart part of this file, see the 45 | # following page: https://dart.dev/tools/pub/pubspec 46 | 47 | # The following section is specific to Flutter packages. 48 | flutter: 49 | 50 | # The following line ensures that the Material Icons font is 51 | # included with your application, so that you can use the icons in 52 | # the material Icons class. 53 | uses-material-design: true 54 | 55 | # To add assets to your application, add an assets section, like this: 56 | # assets: 57 | # - images/a_dot_burr.jpeg 58 | # - images/a_dot_ham.jpeg 59 | 60 | # An image asset can refer to one or more resolution-specific "variants", see 61 | # https://flutter.dev/assets-and-images/#resolution-aware 62 | 63 | # For details regarding adding assets from package dependencies, see 64 | # https://flutter.dev/assets-and-images/#from-packages 65 | 66 | # To add custom fonts to your application, add a fonts section here, 67 | # in this "flutter" section. Each entry in this list should have a 68 | # "family" key with the font family name, and a "fonts" key with a 69 | # list giving the asset and other descriptors for the font. For 70 | # example: 71 | # fonts: 72 | # - family: Schyler 73 | # fonts: 74 | # - asset: fonts/Schyler-Regular.ttf 75 | # - asset: fonts/Schyler-Italic.ttf 76 | # style: italic 77 | # - family: Trajan Pro 78 | # fonts: 79 | # - asset: fonts/TrajanPro.ttf 80 | # - asset: fonts/TrajanPro_Bold.ttf 81 | # weight: 700 82 | # 83 | # For details regarding fonts from package dependencies, 84 | # see https://flutter.dev/custom-fonts/#from-packages 85 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/AndroidImc.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * HSR Hochschule fuer Technik Rapperswil 4 | * 5 | * This program is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License as published by the 7 | * Free Software Foundation; either version 2 of the License, or (at your 8 | * option) any later version. See . 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | */ 15 | 16 | package org.strongswan.android.logic.imc; 17 | 18 | import org.strongswan.android.logic.imc.attributes.Attribute; 19 | import org.strongswan.android.logic.imc.attributes.AttributeType; 20 | import org.strongswan.android.logic.imc.collectors.Collector; 21 | import org.strongswan.android.logic.imc.collectors.DeviceIdCollector; 22 | import org.strongswan.android.logic.imc.collectors.InstalledPackagesCollector; 23 | import org.strongswan.android.logic.imc.collectors.PortFilterCollector; 24 | import org.strongswan.android.logic.imc.collectors.ProductInformationCollector; 25 | import org.strongswan.android.logic.imc.collectors.SettingsCollector; 26 | import org.strongswan.android.logic.imc.collectors.StringVersionCollector; 27 | 28 | import android.content.Context; 29 | 30 | public class AndroidImc 31 | { 32 | private final Context mContext; 33 | 34 | public AndroidImc(Context context) 35 | { 36 | mContext = context; 37 | } 38 | 39 | /** 40 | * Get a measurement (the binary encoding of the requested attribute) for 41 | * the given vendor specific attribute type. 42 | * 43 | * @param vendor vendor ID 44 | * @param type vendor specific attribute type 45 | * @return encoded attribute, or null if not available or failed 46 | */ 47 | public byte[] getMeasurement(int vendor, int type) 48 | { 49 | return getMeasurement(vendor, type, null); 50 | } 51 | 52 | /** 53 | * Get a measurement (the binary encoding of the requested attribute) for 54 | * the given vendor specific attribute type. 55 | * 56 | * @param vendor vendor ID 57 | * @param type vendor specific attribute type 58 | * @param args optional arguments for a measurement 59 | * @return encoded attribute, or null if not available or failed 60 | */ 61 | public byte[] getMeasurement(int vendor, int type, String[] args) 62 | { 63 | AttributeType attributeType = AttributeType.fromValues(vendor, type); 64 | Collector collector = null; 65 | 66 | switch (attributeType) 67 | { 68 | case IETF_PRODUCT_INFORMATION: 69 | collector = new ProductInformationCollector(); 70 | break; 71 | case IETF_STRING_VERSION: 72 | collector = new StringVersionCollector(); 73 | break; 74 | case IETF_PORT_FILTER: 75 | collector = new PortFilterCollector(); 76 | break; 77 | case IETF_INSTALLED_PACKAGES: 78 | collector = new InstalledPackagesCollector(mContext); 79 | break; 80 | case ITA_SETTINGS: 81 | collector = new SettingsCollector(mContext, args); 82 | break; 83 | case ITA_DEVICE_ID: 84 | collector = new DeviceIdCollector(mContext); 85 | break; 86 | default: 87 | break; 88 | } 89 | if (collector != null) 90 | { 91 | Attribute attribute = collector.getMeasurement(); 92 | if (attribute != null) 93 | { 94 | return attribute.getEncoding(); 95 | } 96 | } 97 | return null; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2018-2022 Jason C.H 2 | /// 3 | /// This library is free software; you can redistribute it and/or 4 | /// modify it under the terms of the GNU Lesser General Public 5 | /// License as published by the Free Software Foundation; either 6 | /// version 2.1 of the License, or (at your option) any later version. 7 | /// 8 | /// This library is distributed in the hope that it will be useful, 9 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | /// Lesser General Public License for more details. 12 | import 'package:flutter/material.dart'; 13 | import 'package:flutter_vpn/flutter_vpn.dart'; 14 | import 'package:flutter_vpn/state.dart'; 15 | 16 | void main() => runApp(const MyApp()); 17 | 18 | class MyApp extends StatefulWidget { 19 | const MyApp({Key? key}) : super(key: key); 20 | 21 | @override 22 | State createState() => _MyAppState(); 23 | } 24 | 25 | class _MyAppState extends State { 26 | final _addressController = TextEditingController(); 27 | final _usernameController = TextEditingController(); 28 | final _passwordController = TextEditingController(); 29 | 30 | var state = FlutterVpnState.disconnected; 31 | CharonErrorState? charonState = CharonErrorState.NO_ERROR; 32 | 33 | @override 34 | void initState() { 35 | FlutterVpn.prepare(); 36 | FlutterVpn.onStateChanged.listen((s) => setState(() => state = s)); 37 | super.initState(); 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return MaterialApp( 43 | home: Scaffold( 44 | appBar: AppBar( 45 | title: const Text('Flutter VPN'), 46 | ), 47 | body: ListView( 48 | padding: const EdgeInsets.all(12), 49 | children: [ 50 | Text('Current State: $state'), 51 | Text('Current Charon State: $charonState'), 52 | TextFormField( 53 | controller: _addressController, 54 | decoration: const InputDecoration(icon: Icon(Icons.map_outlined)), 55 | ), 56 | TextFormField( 57 | controller: _usernameController, 58 | decoration: const InputDecoration( 59 | icon: Icon(Icons.person_outline), 60 | ), 61 | ), 62 | TextFormField( 63 | controller: _passwordController, 64 | obscureText: true, 65 | decoration: const InputDecoration(icon: Icon(Icons.lock_outline)), 66 | ), 67 | ElevatedButton( 68 | child: const Text('Connect'), 69 | onPressed: () => FlutterVpn.connectIkev2EAP( 70 | server: _addressController.text, 71 | username: _usernameController.text, 72 | password: _passwordController.text, 73 | ), 74 | ), 75 | ElevatedButton( 76 | child: const Text('Disconnect'), 77 | onPressed: () => FlutterVpn.disconnect(), 78 | ), 79 | ElevatedButton( 80 | child: const Text('Update State'), 81 | onPressed: () async { 82 | var newState = await FlutterVpn.currentState; 83 | setState(() => state = newState); 84 | }, 85 | ), 86 | ElevatedButton( 87 | child: const Text('Update Charon State'), 88 | onPressed: () async { 89 | var newState = await FlutterVpn.charonErrorState; 90 | setState(() => charonState = newState); 91 | }, 92 | ), 93 | ], 94 | ), 95 | ), 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/flutter_vpn_platform_interface.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2018-2022 Jason C.H 2 | /// 3 | /// This library is free software; you can redistribute it and/or 4 | /// modify it under the terms of the GNU Lesser General Public 5 | /// License as published by the Free Software Foundation; either 6 | /// version 2.1 of the License, or (at your option) any later version. 7 | /// 8 | /// This library is distributed in the hope that it will be useful, 9 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | /// Lesser General Public License for more details. 12 | import 'package:plugin_platform_interface/plugin_platform_interface.dart'; 13 | 14 | import 'flutter_vpn_method_channel.dart'; 15 | import 'state.dart'; 16 | 17 | abstract class FlutterVpnPlatform extends PlatformInterface { 18 | /// Constructs a FlutterVpnPlatform. 19 | FlutterVpnPlatform() : super(token: _token); 20 | 21 | static final Object _token = Object(); 22 | 23 | static FlutterVpnPlatform _instance = MethodChannelFlutterVpn(); 24 | 25 | /// The default instance of [FlutterVpnPlatform] to use. 26 | /// 27 | /// Defaults to [MethodChannelFlutterVpn]. 28 | static FlutterVpnPlatform get instance => _instance; 29 | 30 | /// Platform-specific implementations should set this with their own 31 | /// platform-specific class that extends [FlutterVpnPlatform] when 32 | /// they register themselves. 33 | static set instance(FlutterVpnPlatform instance) { 34 | PlatformInterface.verifyToken(instance, _token); 35 | _instance = instance; 36 | } 37 | 38 | /// Receive state change from VPN service. 39 | /// 40 | /// Can only be listened once. If have more than one subscription, only the 41 | /// last subscription can receive events. 42 | Stream get onStateChanged => throw UnimplementedError(); 43 | 44 | /// Get current state. 45 | Future get currentState async => throw UnimplementedError(); 46 | 47 | /// Get current error state from `VpnStateService`. (Android only) 48 | /// When [FlutterVpnState.error] is received, details of error can be 49 | /// inspected by [CharonErrorState]. Returns [null] on non-android platform. 50 | Future get charonErrorState async => throw UnimplementedError(); 51 | 52 | /// Prepare for vpn connection. (Android only) 53 | /// 54 | /// For first connection it will show a dialog to ask for permission. 55 | /// When your connection was interrupted by another VPN connection, 56 | /// you should prepare again before reconnect. 57 | Future prepare() async => throw UnimplementedError(); 58 | 59 | /// Check if vpn connection has been prepared. (Android only) 60 | Future get prepared async => throw UnimplementedError(); 61 | 62 | /// Disconnect and stop VPN service. 63 | Future disconnect() async => throw UnimplementedError(); 64 | 65 | /// Connect to VPN. (IKEv2-EAP) 66 | /// 67 | /// This will create a background VPN service. 68 | /// MTU is only available on android. 69 | Future connectIkev2EAP({ 70 | required String server, 71 | required String username, 72 | required String password, 73 | String? name, 74 | int? mtu, 75 | int? port, 76 | }) async => 77 | throw UnimplementedError(); 78 | 79 | /// Connect to VPN. (IPSec) 80 | /// 81 | /// This will create a background VPN service. 82 | /// Android implementation is not available. 83 | Future connectIPSec({ 84 | required String server, 85 | required String username, 86 | required String password, 87 | required String secret, 88 | String? name, 89 | int? mtu, 90 | int? port, 91 | }) async => 92 | throw UnimplementedError(); 93 | } 94 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/utils/SettingsWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Tobias Brunner 3 | * HSR Hochschule fuer Technik Rapperswil 4 | * 5 | * This program is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License as published by the 7 | * Free Software Foundation; either version 2 of the License, or (at your 8 | * option) any later version. See . 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | */ 15 | 16 | package org.strongswan.android.utils; 17 | 18 | import java.util.Arrays; 19 | import java.util.LinkedHashMap; 20 | import java.util.Map.Entry; 21 | import java.util.regex.Pattern; 22 | 23 | 24 | /** 25 | * Simple generator for data/files that may be parsed by libstrongswan's 26 | * settings_t class. 27 | */ 28 | public class SettingsWriter 29 | { 30 | /** 31 | * Top-level section 32 | */ 33 | private final SettingsSection mTop = new SettingsSection(); 34 | 35 | /** 36 | * Set a string value 37 | * @param key 38 | * @param value 39 | * @return the writer 40 | */ 41 | public SettingsWriter setValue(String key, String value) 42 | { 43 | Pattern pattern = Pattern.compile("[^#{}=\"\\n\\t ]+"); 44 | if (key == null || !pattern.matcher(key).matches()) 45 | { 46 | return this; 47 | } 48 | String[] keys = key.split("\\."); 49 | SettingsSection section = mTop; 50 | section = findOrCreateSection(Arrays.copyOfRange(keys, 0, keys.length-1)); 51 | section.Settings.put(keys[keys.length-1], value); 52 | return this; 53 | } 54 | 55 | /** 56 | * Set an integer value 57 | * @param key 58 | * @param value 59 | * @return the writer 60 | */ 61 | public SettingsWriter setValue(String key, Integer value) 62 | { 63 | return setValue(key, value == null ? null : value.toString()); 64 | } 65 | 66 | /** 67 | * Set a boolean value 68 | * @param key 69 | * @param value 70 | * @return the writer 71 | */ 72 | public SettingsWriter setValue(String key, Boolean value) 73 | { 74 | return setValue(key, value == null ? null : value ? "1" : "0"); 75 | } 76 | 77 | /** 78 | * Serializes the settings to a string in the format understood by 79 | * libstrongswan's settings_t parser. 80 | * @return serialized settings 81 | */ 82 | public String serialize() 83 | { 84 | StringBuilder builder = new StringBuilder(); 85 | serializeSection(mTop, builder); 86 | return builder.toString(); 87 | } 88 | 89 | /** 90 | * Serialize the settings in a section and recursively serialize sub-sections 91 | * @param section 92 | * @param builder 93 | */ 94 | private void serializeSection(SettingsSection section, StringBuilder builder) 95 | { 96 | for (Entry setting : section.Settings.entrySet()) 97 | { 98 | builder.append(setting.getKey()).append('='); 99 | if (setting.getValue() != null) 100 | { 101 | builder.append("\"").append(escapeValue(setting.getValue())).append("\""); 102 | } 103 | builder.append('\n'); 104 | } 105 | 106 | for (Entry subsection : section.Sections.entrySet()) 107 | { 108 | builder.append(subsection.getKey()).append(" {\n"); 109 | serializeSection(subsection.getValue(), builder); 110 | builder.append("}\n"); 111 | } 112 | } 113 | 114 | /** 115 | * Escape value so it may be wrapped in " 116 | * @param value 117 | * @return 118 | */ 119 | private String escapeValue(String value) 120 | { 121 | return value.replace("\\", "\\\\").replace("\"", "\\\""); 122 | } 123 | 124 | /** 125 | * Find or create the nested sections with the given names 126 | * @param sections list of section names 127 | * @return final section 128 | */ 129 | private SettingsSection findOrCreateSection(String[] sections) 130 | { 131 | SettingsSection section = mTop; 132 | for (String name : sections) 133 | { 134 | SettingsSection subsection = section.Sections.get(name); 135 | if (subsection == null) 136 | { 137 | subsection = new SettingsSection(); 138 | section.Sections.put(name, subsection); 139 | } 140 | section = subsection; 141 | } 142 | return section; 143 | } 144 | 145 | /** 146 | * A section containing sub-sections and settings. 147 | */ 148 | private class SettingsSection 149 | { 150 | /** 151 | * Assigned key/value pairs 152 | */ 153 | LinkedHashMap Settings = new LinkedHashMap(); 154 | 155 | /** 156 | * Assigned sub-sections 157 | */ 158 | LinkedHashMap Sections = new LinkedHashMap(); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/SimpleFetcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2018 Tobias Brunner 3 | * HSR Hochschule fuer Technik Rapperswil 4 | * 5 | * This program is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License as published by the 7 | * Free Software Foundation; either version 2 of the License, or (at your 8 | * option) any later version. See . 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | */ 15 | 16 | package org.strongswan.android.logic; 17 | 18 | import java.io.BufferedOutputStream; 19 | import java.io.ByteArrayOutputStream; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.io.OutputStream; 23 | import java.net.HttpURLConnection; 24 | import java.net.SocketTimeoutException; 25 | import java.net.URL; 26 | import java.util.ArrayList; 27 | import java.util.concurrent.CancellationException; 28 | import java.util.concurrent.ExecutionException; 29 | import java.util.concurrent.ExecutorService; 30 | import java.util.concurrent.Executors; 31 | import java.util.concurrent.Future; 32 | import java.util.concurrent.TimeUnit; 33 | import java.util.concurrent.TimeoutException; 34 | 35 | import androidx.annotation.Keep; 36 | 37 | @Keep 38 | public class SimpleFetcher 39 | { 40 | private static ExecutorService mExecutor = Executors.newCachedThreadPool(); 41 | private static Object mLock = new Object(); 42 | private static ArrayList mFutures = new ArrayList<>(); 43 | private static boolean mDisabled; 44 | 45 | public static byte[] fetch(String uri, byte[] data, String contentType) 46 | { 47 | Future future; 48 | 49 | synchronized (mLock) 50 | { 51 | if (mDisabled) 52 | { 53 | return null; 54 | } 55 | future = mExecutor.submit(() -> { 56 | URL url = new URL(uri); 57 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 58 | conn.setConnectTimeout(10000); 59 | conn.setReadTimeout(10000); 60 | try 61 | { 62 | if (contentType != null) 63 | { 64 | conn.setRequestProperty("Content-Type", contentType); 65 | } 66 | if (data != null) 67 | { 68 | conn.setDoOutput(true); 69 | conn.setFixedLengthStreamingMode(data.length); 70 | OutputStream out = new BufferedOutputStream(conn.getOutputStream()); 71 | out.write(data); 72 | out.close(); 73 | } 74 | return streamToArray(conn.getInputStream()); 75 | } 76 | catch (SocketTimeoutException e) 77 | { 78 | return null; 79 | } 80 | finally 81 | { 82 | conn.disconnect(); 83 | } 84 | }); 85 | 86 | mFutures.add(future); 87 | } 88 | 89 | try 90 | { 91 | /* this enforces a timeout as the ones set on HttpURLConnection might not work reliably */ 92 | return future.get(10000, TimeUnit.MILLISECONDS); 93 | } 94 | catch (InterruptedException|ExecutionException|TimeoutException|CancellationException e) 95 | { 96 | return null; 97 | } 98 | finally 99 | { 100 | synchronized (mLock) 101 | { 102 | mFutures.remove(future); 103 | } 104 | } 105 | } 106 | 107 | /** 108 | * Enable fetching after it has been disabled. 109 | */ 110 | public static void enable() 111 | { 112 | synchronized (mLock) 113 | { 114 | mDisabled = false; 115 | } 116 | } 117 | 118 | /** 119 | * Disable the fetcher and abort any future requests. 120 | * 121 | * The native thread is not cancelable as it is working on an IKE_SA (canceling the methods of 122 | * HttpURLConnection is not reliably possible anyway), so to abort while fetching we cancel the 123 | * Future (causing a return from fetch() immediately) and let the executor thread continue its 124 | * thing in the background. 125 | * 126 | * Also prevents future fetches until enabled again (e.g. if we aborted OCSP but would then 127 | * block in the subsequent fetch for a CRL). 128 | */ 129 | public static void disable() 130 | { 131 | synchronized (mLock) 132 | { 133 | mDisabled = true; 134 | for (Future future : mFutures) 135 | { 136 | future.cancel(true); 137 | } 138 | } 139 | } 140 | 141 | private static byte[] streamToArray(InputStream in) throws IOException 142 | { 143 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 144 | byte[] buf = new byte[1024]; 145 | int len; 146 | 147 | try 148 | { 149 | while ((len = in.read(buf)) != -1) 150 | { 151 | out.write(buf, 0, len); 152 | } 153 | return out.toByteArray(); 154 | } 155 | catch (IOException e) 156 | { 157 | e.printStackTrace(); 158 | } 159 | finally 160 | { 161 | in.close(); 162 | } 163 | return null; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /lib/flutter_vpn_method_channel.dart: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2018-2022 Jason C.H 2 | /// 3 | /// This library is free software; you can redistribute it and/or 4 | /// modify it under the terms of the GNU Lesser General Public 5 | /// License as published by the Free Software Foundation; either 6 | /// version 2.1 of the License, or (at your option) any later version. 7 | /// 8 | /// This library is distributed in the hope that it will be useful, 9 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | /// Lesser General Public License for more details. 12 | import 'dart:io'; 13 | 14 | import 'package:flutter/foundation.dart'; 15 | import 'package:flutter/services.dart'; 16 | import 'package:flutter_vpn/state.dart'; 17 | 18 | import 'flutter_vpn_platform_interface.dart'; 19 | 20 | /// An implementation of [FlutterVpnPlatform] that uses method channels. 21 | class MethodChannelFlutterVpn extends FlutterVpnPlatform { 22 | /// The method channel used to interact with the native platform. 23 | @visibleForTesting 24 | final methodChannel = const MethodChannel('flutter_vpn'); 25 | 26 | /// The method channel used to receive state change event. 27 | @visibleForTesting 28 | final eventChannel = const EventChannel('flutter_vpn_states'); 29 | 30 | /// Receive state change from VPN service. 31 | /// 32 | /// Can only be listened once. If have more than one subscription, only the 33 | /// last subscription can receive events. 34 | @override 35 | Stream get onStateChanged => 36 | eventChannel.receiveBroadcastStream().map((e) => FlutterVpnState.values[e]); 37 | 38 | /// Get current state. 39 | @override 40 | Future get currentState async { 41 | final state = await methodChannel.invokeMethod('getCurrentState'); 42 | assert(state != null, 'Received a null state from `getCurrentState` call.'); 43 | return FlutterVpnState.values[state!]; 44 | } 45 | 46 | /// Get current error state from `VpnStateService`. (Android only) 47 | /// When [FlutterVpnState.error] is received, details of error can be 48 | /// inspected by [CharonErrorState]. Returns [null] on non-android platform. 49 | @override 50 | Future get charonErrorState async { 51 | if (!Platform.isAndroid) return null; 52 | var state = await methodChannel.invokeMethod('getCharonErrorState'); 53 | assert( 54 | state != null, 55 | 'Received a null state from `getCharonErrorState` call.', 56 | ); 57 | return CharonErrorState.values[state!]; 58 | } 59 | 60 | /// Prepare for vpn connection. (Android only) 61 | /// 62 | /// For first connection it will show a dialog to ask for permission. 63 | /// When your connection was interrupted by another VPN connection, 64 | /// you should prepare again before reconnect. 65 | @override 66 | Future prepare() async { 67 | if (!Platform.isAndroid) return true; 68 | return (await methodChannel.invokeMethod('prepare'))!; 69 | } 70 | 71 | /// Check if vpn connection has been prepared. (Android only) 72 | @override 73 | Future get prepared async { 74 | if (!Platform.isAndroid) return true; 75 | return (await methodChannel.invokeMethod('prepared'))!; 76 | } 77 | 78 | /// Disconnect and stop VPN service. 79 | @override 80 | Future disconnect() async { 81 | await methodChannel.invokeMethod('disconnect'); 82 | } 83 | 84 | /// Connect to VPN. (IKEv2-EAP) 85 | /// 86 | /// This will create a background VPN service. 87 | /// MTU is only available on android. 88 | @override 89 | Future connectIkev2EAP({ 90 | required String server, 91 | required String username, 92 | required String password, 93 | String? name, 94 | int? mtu, 95 | int? port, 96 | }) async => 97 | await methodChannel.invokeMethod('connect', { 98 | 'Type': 'IKEv2', 99 | 'Server': server, 100 | 'Username': username, 101 | 'Password': password, 102 | 'Secret': '', 103 | 'Name': name ?? server, 104 | if (mtu != null) 'mtu': mtu, 105 | if (port != null) 'port': port, 106 | }); 107 | 108 | /// Connect to VPN. (IPSec) 109 | /// 110 | /// This will create a background VPN service. 111 | /// Android implementation is not available. 112 | @override 113 | Future connectIPSec({ 114 | required String server, 115 | required String username, 116 | required String password, 117 | required String secret, 118 | String? name, 119 | int? mtu, 120 | int? port, 121 | }) async => 122 | await methodChannel.invokeMethod('connect', { 123 | 'Type': 'IPSec', 124 | 'Server': server, 125 | 'Username': username, 126 | 'Password': password, 127 | 'Secret': secret, 128 | 'Name': name ?? server, 129 | if (mtu != null) 'mtu': mtu, 130 | if (port != null) 'port': port, 131 | }); 132 | } 133 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.8.2" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.2.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.16.0" 46 | cupertino_icons: 47 | dependency: "direct main" 48 | description: 49 | name: cupertino_icons 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.0.4" 53 | fake_async: 54 | dependency: transitive 55 | description: 56 | name: fake_async 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.3.0" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_lints: 66 | dependency: "direct dev" 67 | description: 68 | name: flutter_lints 69 | url: "https://pub.dartlang.org" 70 | source: hosted 71 | version: "2.0.1" 72 | flutter_test: 73 | dependency: "direct dev" 74 | description: flutter 75 | source: sdk 76 | version: "0.0.0" 77 | flutter_vpn: 78 | dependency: "direct main" 79 | description: 80 | path: ".." 81 | relative: true 82 | source: path 83 | version: "0.12.0" 84 | lints: 85 | dependency: transitive 86 | description: 87 | name: lints 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "2.0.0" 91 | matcher: 92 | dependency: transitive 93 | description: 94 | name: matcher 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "0.12.11" 98 | material_color_utilities: 99 | dependency: transitive 100 | description: 101 | name: material_color_utilities 102 | url: "https://pub.dartlang.org" 103 | source: hosted 104 | version: "0.1.4" 105 | meta: 106 | dependency: transitive 107 | description: 108 | name: meta 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "1.7.0" 112 | path: 113 | dependency: transitive 114 | description: 115 | name: path 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "1.8.1" 119 | plugin_platform_interface: 120 | dependency: transitive 121 | description: 122 | name: plugin_platform_interface 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "2.1.2" 126 | sky_engine: 127 | dependency: transitive 128 | description: flutter 129 | source: sdk 130 | version: "0.0.99" 131 | source_span: 132 | dependency: transitive 133 | description: 134 | name: source_span 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "1.8.2" 138 | stack_trace: 139 | dependency: transitive 140 | description: 141 | name: stack_trace 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "1.10.0" 145 | stream_channel: 146 | dependency: transitive 147 | description: 148 | name: stream_channel 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "2.1.0" 152 | string_scanner: 153 | dependency: transitive 154 | description: 155 | name: string_scanner 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "1.1.0" 159 | term_glyph: 160 | dependency: transitive 161 | description: 162 | name: term_glyph 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "1.2.0" 166 | test_api: 167 | dependency: transitive 168 | description: 169 | name: test_api 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "0.4.9" 173 | vector_math: 174 | dependency: transitive 175 | description: 176 | name: vector_math 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "2.1.2" 180 | sdks: 181 | dart: ">=2.17.0-206.0.dev <3.0.0" 182 | flutter: ">=2.5.0" 183 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/Scheduler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Tobias Brunner 3 | * HSR Hochschule fuer Technik Rapperswil 4 | * 5 | * This program is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License as published by the 7 | * Free Software Foundation; either version 2 of the License, or (at your 8 | * option) any later version. See . 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | */ 15 | 16 | package org.strongswan.android.logic; 17 | 18 | import android.app.AlarmManager; 19 | import android.app.PendingIntent; 20 | import android.content.BroadcastReceiver; 21 | import android.content.Context; 22 | import android.content.Intent; 23 | import android.content.IntentFilter; 24 | import android.os.Build; 25 | 26 | import java.util.ArrayList; 27 | import java.util.PriorityQueue; 28 | import java.util.UUID; 29 | 30 | import androidx.annotation.RequiresApi; 31 | 32 | public class Scheduler extends BroadcastReceiver 33 | { 34 | private final String EXECUTE_JOB = "org.strongswan.android.Scheduler.EXECUTE_JOB"; 35 | private final Context mContext; 36 | private final AlarmManager mManager; 37 | private final PriorityQueue mJobs; 38 | 39 | public Scheduler(Context context) 40 | { 41 | mContext = context; 42 | mManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); 43 | mJobs = new PriorityQueue<>(); 44 | 45 | IntentFilter filter = new IntentFilter(); 46 | filter.addAction(EXECUTE_JOB); 47 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 48 | mContext.registerReceiver(this, filter, Context.RECEIVER_EXPORTED); 49 | } else { 50 | mContext.registerReceiver(this, filter); 51 | } 52 | } 53 | 54 | /** 55 | * Remove all pending jobs and unregister the receiver. 56 | * Called via JNI. 57 | */ 58 | public void Terminate() 59 | { 60 | synchronized (this) 61 | { 62 | mJobs.clear(); 63 | } 64 | mManager.cancel(createIntent()); 65 | mContext.unregisterReceiver(this); 66 | } 67 | 68 | /** 69 | * Allocate a job ID. Called via JNI. 70 | * 71 | * @return random ID for a new job 72 | */ 73 | public String allocateId() 74 | { 75 | return UUID.randomUUID().toString(); 76 | } 77 | 78 | /** 79 | * Create a pending intent to execute a job. 80 | * 81 | * @return pending intent 82 | */ 83 | private PendingIntent createIntent() 84 | { 85 | /* using component/class doesn't work with dynamic broadcast receivers */ 86 | Intent intent = new Intent(EXECUTE_JOB); 87 | intent.setPackage(mContext.getPackageName()); 88 | return PendingIntent.getBroadcast(mContext, 0, intent, 0); 89 | } 90 | 91 | /** 92 | * Schedule executing a job in the future. 93 | * Called via JNI from different threads. 94 | * 95 | * @param id job ID 96 | * @param ms delta in milliseconds when the job should be executed 97 | */ 98 | @RequiresApi(api = Build.VERSION_CODES.M) 99 | public void scheduleJob(String id, long ms) 100 | { 101 | synchronized (this) 102 | { 103 | ScheduledJob job = new ScheduledJob(id, System.currentTimeMillis() + ms); 104 | mJobs.add(job); 105 | 106 | if (job == mJobs.peek()) 107 | { /* update the alarm if the job has to be executed before all others */ 108 | PendingIntent pending = createIntent(); 109 | mManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, job.Time, pending); 110 | } 111 | } 112 | } 113 | 114 | @RequiresApi(api = Build.VERSION_CODES.M) 115 | @Override 116 | public void onReceive(Context context, Intent intent) 117 | { 118 | ArrayList jobs = new ArrayList<>(); 119 | long now = System.currentTimeMillis(); 120 | 121 | synchronized (this) 122 | { 123 | ScheduledJob job = mJobs.peek(); 124 | while (job != null) 125 | { 126 | if (job.Time > now) 127 | { 128 | break; 129 | } 130 | jobs.add(mJobs.remove()); 131 | job = mJobs.peek(); 132 | } 133 | if (job != null) 134 | { 135 | PendingIntent pending = createIntent(); 136 | mManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, job.Time, pending); 137 | } 138 | } 139 | 140 | for (ScheduledJob job : jobs) 141 | { 142 | executeJob(job.Id); 143 | } 144 | } 145 | 146 | /** 147 | * Execute the job with the given ID. 148 | * 149 | * @param id job ID 150 | */ 151 | public native void executeJob(String id); 152 | 153 | /** 154 | * Keep track of scheduled jobs. 155 | */ 156 | private static class ScheduledJob implements Comparable 157 | { 158 | String Id; 159 | long Time; 160 | 161 | ScheduledJob(String id, long time) 162 | { 163 | Id = id; 164 | Time = time; 165 | } 166 | 167 | @Override 168 | public int compareTo(ScheduledJob o) 169 | { 170 | return Long.compare(Time, o.Time); 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/NetworkManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2019 Tobias Brunner 3 | * HSR Hochschule fuer Technik Rapperswil 4 | * 5 | * This program is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License as published by the 7 | * Free Software Foundation; either version 2 of the License, or (at your 8 | * option) any later version. See . 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | */ 15 | 16 | package org.strongswan.android.logic; 17 | 18 | import android.content.BroadcastReceiver; 19 | import android.content.Context; 20 | import android.content.Intent; 21 | import android.content.IntentFilter; 22 | import android.net.ConnectivityManager; 23 | import android.net.Network; 24 | import android.net.NetworkRequest; 25 | import android.os.Build; 26 | 27 | import java.util.LinkedList; 28 | 29 | public class NetworkManager extends BroadcastReceiver implements Runnable 30 | { 31 | private final Context mContext; 32 | private volatile boolean mRegistered; 33 | private ConnectivityManager.NetworkCallback mCallback; 34 | private Thread mEventNotifier; 35 | private int mConnectedNetworks = 0; 36 | private LinkedList mEvents = new LinkedList<>(); 37 | 38 | public NetworkManager(Context context) 39 | { 40 | mContext = context; 41 | 42 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) 43 | { 44 | mCallback = new ConnectivityManager.NetworkCallback() 45 | { 46 | @Override 47 | public void onAvailable(Network network) 48 | { 49 | synchronized (NetworkManager.this) 50 | { 51 | /* we expect this to be called if connected to at least one network during 52 | * callback registration */ 53 | mConnectedNetworks += 1; 54 | mEvents.addLast(true); 55 | NetworkManager.this.notifyAll(); 56 | } 57 | } 58 | 59 | @Override 60 | public void onLost(Network network) 61 | { 62 | synchronized (NetworkManager.this) 63 | { 64 | /* in particular mobile connections are disconnected overlapping with WiFi */ 65 | mConnectedNetworks -= 1; 66 | mEvents.addLast(mConnectedNetworks > 0); 67 | NetworkManager.this.notifyAll(); 68 | } 69 | } 70 | }; 71 | } 72 | } 73 | 74 | public void Register() 75 | { 76 | mEvents.clear(); 77 | mRegistered = true; 78 | mEventNotifier = new Thread(this); 79 | mEventNotifier.start(); 80 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) 81 | { 82 | ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); 83 | /* while we only get events for the VPN network via registerDefaultNetworkCallback, 84 | * the default capabilities in the builder include NetworkCapabilities.NET_CAPABILITY_NOT_VPN */ 85 | NetworkRequest.Builder builder = new NetworkRequest.Builder(); 86 | cm.registerNetworkCallback(builder.build(), mCallback); 87 | } 88 | else 89 | { 90 | registerLegacyReceiver(); 91 | } 92 | } 93 | 94 | @SuppressWarnings("deprecation") 95 | private void registerLegacyReceiver() 96 | { 97 | /* deprecated since API level 28 */ 98 | mContext.registerReceiver(this, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); 99 | } 100 | 101 | public void Unregister() 102 | { 103 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) 104 | { 105 | ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); 106 | cm.unregisterNetworkCallback(mCallback); 107 | } 108 | else 109 | { 110 | mContext.unregisterReceiver(this); 111 | } 112 | mRegistered = false; 113 | synchronized (this) 114 | { 115 | notifyAll(); 116 | } 117 | try 118 | { 119 | mEventNotifier.join(); 120 | mEventNotifier = null; 121 | } 122 | catch (InterruptedException e) 123 | { 124 | e.printStackTrace(); 125 | } 126 | } 127 | 128 | @SuppressWarnings("deprecation") 129 | public boolean isConnected() 130 | { 131 | /* deprecated since API level 29 */ 132 | ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE); 133 | android.net.NetworkInfo info = null; 134 | if (cm != null) 135 | { 136 | info = cm.getActiveNetworkInfo(); 137 | } 138 | return info != null && info.isConnected(); 139 | } 140 | 141 | @Override 142 | public void onReceive(Context context, Intent intent) 143 | { 144 | synchronized (this) 145 | { 146 | mEvents.addLast(isConnected()); 147 | notifyAll(); 148 | } 149 | } 150 | 151 | @Override 152 | public void run() 153 | { 154 | while (mRegistered) 155 | { 156 | boolean connected; 157 | 158 | synchronized (this) 159 | { 160 | try 161 | { 162 | while (mRegistered && mEvents.isEmpty()) 163 | { 164 | wait(); 165 | } 166 | } 167 | catch (InterruptedException ex) 168 | { 169 | break; 170 | } 171 | if (!mRegistered) 172 | { 173 | break; 174 | } 175 | connected = mEvents.removeFirst(); 176 | } 177 | /* call the native parts without holding the lock */ 178 | networkChanged(!connected); 179 | } 180 | } 181 | 182 | /** 183 | * Notify the native parts about a network change 184 | * 185 | * @param disconnected true if no connection is available at the moment 186 | */ 187 | public native void networkChanged(boolean disconnected); 188 | } 189 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/utils/IPRangeSet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2017 Tobias Brunner 3 | * HSR Hochschule fuer Technik Rapperswil 4 | * 5 | * This program is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License as published by the 7 | * Free Software Foundation; either version 2 of the License, or (at your 8 | * option) any later version. See . 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | */ 15 | 16 | package org.strongswan.android.utils; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Collection; 20 | import java.util.Iterator; 21 | import java.util.List; 22 | import java.util.TreeSet; 23 | 24 | /** 25 | * Class that represents a set of IP address ranges (not necessarily proper subnets) and allows 26 | * modifying the set and enumerating the resulting subnets. 27 | */ 28 | public class IPRangeSet implements Iterable 29 | { 30 | private TreeSet mRanges = new TreeSet<>(); 31 | 32 | /** 33 | * Parse the given string (space separated ranges in CIDR or range notation) and return the 34 | * resulting set or {@code null} if the string was invalid. An empty set is returned if the given string 35 | * is {@code null}. 36 | */ 37 | public static IPRangeSet fromString(String ranges) 38 | { 39 | IPRangeSet set = new IPRangeSet(); 40 | if (ranges != null) 41 | { 42 | for (String range : ranges.split("\\s+")) 43 | { 44 | try 45 | { 46 | set.add(new IPRange(range)); 47 | } 48 | catch (Exception unused) 49 | { /* besides due to invalid strings exceptions might get thrown if the string 50 | * contains a hostname (NetworkOnMainThreadException) */ 51 | return null; 52 | } 53 | } 54 | } 55 | return set; 56 | } 57 | 58 | /** 59 | * Add a range to this set. Automatically gets merged with existing ranges. 60 | */ 61 | public void add(IPRange range) 62 | { 63 | if (mRanges.contains(range)) 64 | { 65 | return; 66 | } 67 | reinsert: 68 | while (true) 69 | { 70 | Iterator iterator = mRanges.iterator(); 71 | while (iterator.hasNext()) 72 | { 73 | IPRange existing = iterator.next(); 74 | IPRange replacement = existing.merge(range); 75 | if (replacement != null) 76 | { 77 | iterator.remove(); 78 | range = replacement; 79 | continue reinsert; 80 | } 81 | } 82 | mRanges.add(range); 83 | break; 84 | } 85 | } 86 | 87 | /** 88 | * Add all ranges from the given set. 89 | */ 90 | public void add(IPRangeSet ranges) 91 | { 92 | if (ranges == this) 93 | { 94 | return; 95 | } 96 | for (IPRange range : ranges.mRanges) 97 | { 98 | add(range); 99 | } 100 | } 101 | 102 | /** 103 | * Add all ranges from the given collection to this set. 104 | */ 105 | public void addAll(Collection coll) 106 | { 107 | for (IPRange range : coll) 108 | { 109 | add(range); 110 | } 111 | } 112 | 113 | /** 114 | * Remove the given range from this set. Existing ranges are automatically adjusted. 115 | */ 116 | public void remove(IPRange range) 117 | { 118 | ArrayList additions = new ArrayList<>(); 119 | Iterator iterator = mRanges.iterator(); 120 | while (iterator.hasNext()) 121 | { 122 | IPRange existing = iterator.next(); 123 | List result = existing.remove(range); 124 | if (result.size() == 0) 125 | { 126 | iterator.remove(); 127 | } 128 | else if (!result.get(0).equals(existing)) 129 | { 130 | iterator.remove(); 131 | additions.addAll(result); 132 | } 133 | } 134 | mRanges.addAll(additions); 135 | } 136 | 137 | /** 138 | * Remove the given ranges from ranges in this set. 139 | */ 140 | public void remove(IPRangeSet ranges) 141 | { 142 | if (ranges == this) 143 | { 144 | mRanges.clear(); 145 | return; 146 | } 147 | for (IPRange range : ranges.mRanges) 148 | { 149 | remove(range); 150 | } 151 | } 152 | 153 | /** 154 | * Get all the subnets derived from all the ranges in this set. 155 | */ 156 | public Iterable subnets() 157 | { 158 | return new Iterable() 159 | { 160 | @Override 161 | public Iterator iterator() 162 | { 163 | return new Iterator() 164 | { 165 | private Iterator mIterator = mRanges.iterator(); 166 | private List mSubnets; 167 | 168 | @Override 169 | public boolean hasNext() 170 | { 171 | return (mSubnets != null && mSubnets.size() > 0) || mIterator.hasNext(); 172 | } 173 | 174 | @Override 175 | public IPRange next() 176 | { 177 | if (mSubnets == null || mSubnets.size() == 0) 178 | { 179 | IPRange range = mIterator.next(); 180 | mSubnets = range.toSubnets(); 181 | } 182 | return mSubnets.remove(0); 183 | } 184 | 185 | @Override 186 | public void remove() 187 | { 188 | throw new UnsupportedOperationException(); 189 | } 190 | }; 191 | } 192 | }; 193 | } 194 | 195 | @Override 196 | public Iterator iterator() 197 | { 198 | return mRanges.iterator(); 199 | } 200 | 201 | /** 202 | * Returns the number of ranges, not subnets. 203 | */ 204 | public int size() 205 | { 206 | return mRanges.size(); 207 | } 208 | 209 | @Override 210 | public String toString() 211 | { /* we could use TextUtils, but that causes the unit tests to fail */ 212 | StringBuilder sb = new StringBuilder(); 213 | for (IPRange range : mRanges) 214 | { 215 | if (sb.length() > 0) 216 | { 217 | sb.append(" "); 218 | } 219 | sb.append(range.toString()); 220 | } 221 | return sb.toString(); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /android/src/main/kotlin/io/xdea/flutter_vpn/FlutterVpnPlugin.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2018-2022 Jason C.H 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | */ 14 | 15 | package io.xdea.flutter_vpn 16 | 17 | import android.app.Activity.RESULT_OK 18 | import android.app.Service 19 | import android.content.ComponentName 20 | import android.content.Intent 21 | import android.content.ServiceConnection 22 | import android.net.VpnService 23 | import android.os.Bundle 24 | import android.os.IBinder 25 | import androidx.annotation.NonNull 26 | 27 | import io.flutter.embedding.engine.plugins.FlutterPlugin 28 | import io.flutter.embedding.engine.plugins.activity.ActivityAware 29 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding 30 | import io.flutter.plugin.common.EventChannel 31 | import io.flutter.plugin.common.MethodCall 32 | import io.flutter.plugin.common.MethodChannel 33 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler 34 | import io.flutter.plugin.common.MethodChannel.Result 35 | import io.flutter.plugin.common.PluginRegistry 36 | import org.strongswan.android.logic.VpnStateService 37 | 38 | class FlutterVpnPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { 39 | private lateinit var activityBinding: ActivityPluginBinding 40 | 41 | /// The MethodChannel that will the communication between Flutter and native Android 42 | /// 43 | /// This local reference serves to register the plugin with the Flutter Engine and unregister it 44 | /// when the Flutter Engine is detached from the Activity 45 | private lateinit var channel: MethodChannel 46 | private lateinit var eventChannel: EventChannel 47 | 48 | private var vpnStateService: VpnStateService? = null 49 | private val vpnStateServiceConnection = object : ServiceConnection { 50 | override fun onServiceConnected(name: ComponentName, service: IBinder) { 51 | vpnStateService = (service as VpnStateService.LocalBinder).service 52 | VpnStateHandler.vpnStateService = vpnStateService 53 | vpnStateService?.registerListener(VpnStateHandler) 54 | } 55 | 56 | override fun onServiceDisconnected(name: ComponentName) { 57 | vpnStateService = null 58 | VpnStateHandler.vpnStateService = null 59 | } 60 | } 61 | 62 | override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { 63 | // Load charon bridge 64 | System.loadLibrary("androidbridge") 65 | 66 | // Register method channel. 67 | channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_vpn") 68 | channel.setMethodCallHandler(this) 69 | 70 | // Register event channel to handle state change. 71 | eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "flutter_vpn_states") 72 | eventChannel.setStreamHandler(VpnStateHandler) 73 | 74 | flutterPluginBinding.applicationContext.bindService( 75 | Intent(flutterPluginBinding.applicationContext, VpnStateService::class.java), 76 | vpnStateServiceConnection, 77 | Service.BIND_AUTO_CREATE 78 | ) 79 | } 80 | 81 | override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { 82 | channel.setMethodCallHandler(null) 83 | eventChannel.setStreamHandler(null) 84 | } 85 | 86 | override fun onAttachedToActivity(binding: ActivityPluginBinding) { 87 | activityBinding = binding 88 | } 89 | 90 | override fun onDetachedFromActivity() { 91 | } 92 | 93 | override fun onDetachedFromActivityForConfigChanges() { 94 | } 95 | 96 | override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { 97 | activityBinding = binding 98 | } 99 | 100 | override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { 101 | when (call.method) { 102 | "prepare" -> { 103 | val intent = VpnService.prepare(activityBinding.activity.applicationContext) 104 | if (intent != null) { 105 | var listener: PluginRegistry.ActivityResultListener? = null 106 | listener = PluginRegistry.ActivityResultListener { req, res, _ -> 107 | result.success(req == 0 && res == RESULT_OK) 108 | listener?.let { activityBinding.removeActivityResultListener(it) } 109 | true 110 | } 111 | activityBinding.addActivityResultListener(listener) 112 | activityBinding.activity.startActivityForResult(intent, 0) 113 | } else { 114 | // Already prepared if intent is null. 115 | result.success(true) 116 | } 117 | } 118 | "prepared" -> { 119 | val intent = VpnService.prepare(activityBinding.activity.applicationContext) 120 | result.success(intent == null) 121 | } 122 | "connect" -> { 123 | val intent = VpnService.prepare(activityBinding.activity.applicationContext) 124 | if (intent != null) { 125 | // Not prepared yet. 126 | result.success(false) 127 | return 128 | } 129 | 130 | val args = call.arguments as Map<*, *> 131 | 132 | val profileInfo = Bundle() 133 | profileInfo.putString("VpnType", "ikev2-eap") 134 | profileInfo.putString("Name", args["Name"] as String) 135 | profileInfo.putString("Server", args["Server"] as String) 136 | profileInfo.putString("Username", args["Username"] as String) 137 | profileInfo.putString("Password", args["Password"] as String) 138 | if (args.containsKey("MTU")) profileInfo.putInt("MTU", args["MTU"] as Int) 139 | if (args.containsKey("Port")) profileInfo.putInt("Port", args["Port"] as Int) 140 | 141 | vpnStateService?.connect(profileInfo, true) 142 | result.success(true) 143 | } 144 | "getCurrentState" -> { 145 | if (vpnStateService?.errorState != VpnStateService.ErrorState.NO_ERROR) 146 | result.success(4) 147 | else 148 | result.success(vpnStateService?.state?.ordinal) 149 | } 150 | "getCharonErrorState" -> result.success(vpnStateService?.errorState?.ordinal) 151 | "disconnect" -> vpnStateService?.disconnect() 152 | else -> result.notImplemented() 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/utils/BufferedByteWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * HSR Hochschule fuer Technik Rapperswil 4 | * 5 | * This program is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License as published by the 7 | * Free Software Foundation; either version 2 of the License, or (at your 8 | * option) any later version. See . 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | */ 15 | 16 | package org.strongswan.android.utils; 17 | 18 | import java.nio.ByteBuffer; 19 | 20 | /** 21 | * Very similar to ByteBuffer (although with a stripped interface) but it 22 | * automatically resizes the underlying buffer. 23 | */ 24 | public class BufferedByteWriter 25 | { 26 | /** 27 | * The underlying byte buffer 28 | */ 29 | private byte[] mBuffer; 30 | 31 | /** 32 | * ByteBuffer used as wrapper around the buffer to easily convert values 33 | */ 34 | private ByteBuffer mWriter; 35 | 36 | /** 37 | * Create a writer with a default initial capacity 38 | */ 39 | public BufferedByteWriter() 40 | { 41 | this(0); 42 | } 43 | 44 | /** 45 | * Create a writer with the given initial capacity (helps avoid expensive 46 | * resizing if known). 47 | * @param capacity initial capacity 48 | */ 49 | public BufferedByteWriter(int capacity) 50 | { 51 | capacity = capacity > 4 ? capacity : 32; 52 | mBuffer = new byte[capacity]; 53 | mWriter = ByteBuffer.wrap(mBuffer); 54 | } 55 | 56 | /** 57 | * Ensure that there is enough space available to write the requested 58 | * number of bytes. If necessary the internal buffer is resized. 59 | * @param required required number of bytes 60 | */ 61 | private void ensureCapacity(int required) 62 | { 63 | if (mWriter.remaining() >= required) 64 | { 65 | return; 66 | } 67 | byte[] buffer = new byte[(mBuffer.length + required) * 2]; 68 | System.arraycopy(mBuffer, 0, buffer, 0, mWriter.position()); 69 | mBuffer = buffer; 70 | ByteBuffer writer = ByteBuffer.wrap(buffer); 71 | writer.position(mWriter.position()); 72 | mWriter = writer; 73 | } 74 | 75 | /** 76 | * Write the given byte array to the buffer 77 | * @param value 78 | * @return the writer 79 | */ 80 | public BufferedByteWriter put(byte[] value) 81 | { 82 | ensureCapacity(value.length); 83 | mWriter.put(value); 84 | return this; 85 | } 86 | 87 | /** 88 | * Write the given byte to the buffer 89 | * @param value 90 | * @return the writer 91 | */ 92 | public BufferedByteWriter put(byte value) 93 | { 94 | ensureCapacity(1); 95 | mWriter.put(value); 96 | return this; 97 | } 98 | 99 | /** 100 | * Write the 8-bit length of the given data followed by the data itself 101 | * @param value 102 | * @return the writer 103 | */ 104 | public BufferedByteWriter putLen8(byte[] value) 105 | { 106 | ensureCapacity(1 + value.length); 107 | mWriter.put((byte)value.length); 108 | mWriter.put(value); 109 | return this; 110 | } 111 | 112 | /** 113 | * Write the 16-bit length of the given data followed by the data itself 114 | * @param value 115 | * @return the writer 116 | */ 117 | public BufferedByteWriter putLen16(byte[] value) 118 | { 119 | ensureCapacity(2 + value.length); 120 | mWriter.putShort((short)value.length); 121 | mWriter.put(value); 122 | return this; 123 | } 124 | 125 | /** 126 | * Write the given short value (16-bit) in big-endian order to the buffer 127 | * @param value 128 | * @return the writer 129 | */ 130 | public BufferedByteWriter put16(byte value) 131 | { 132 | return this.put16((short)(value & 0xFF)); 133 | } 134 | 135 | /** 136 | * Write the given short value (16-bit) in big-endian order to the buffer 137 | * @param value 138 | * @return the writer 139 | */ 140 | public BufferedByteWriter put16(short value) 141 | { 142 | ensureCapacity(2); 143 | mWriter.putShort(value); 144 | return this; 145 | } 146 | 147 | /** 148 | * Write 24-bit of the given value in big-endian order to the buffer 149 | * @param value 150 | * @return the writer 151 | */ 152 | public BufferedByteWriter put24(byte value) 153 | { 154 | ensureCapacity(3); 155 | mWriter.putShort((short)0); 156 | mWriter.put(value); 157 | return this; 158 | } 159 | 160 | /** 161 | * Write 24-bit of the given value in big-endian order to the buffer 162 | * @param value 163 | * @return the writer 164 | */ 165 | public BufferedByteWriter put24(short value) 166 | { 167 | ensureCapacity(3); 168 | mWriter.put((byte)0); 169 | mWriter.putShort(value); 170 | return this; 171 | } 172 | 173 | /** 174 | * Write 24-bit of the given value in big-endian order to the buffer 175 | * @param value 176 | * @return the writer 177 | */ 178 | public BufferedByteWriter put24(int value) 179 | { 180 | ensureCapacity(3); 181 | mWriter.put((byte)(value >> 16)); 182 | mWriter.putShort((short)value); 183 | return this; 184 | } 185 | 186 | /** 187 | * Write the given int value (32-bit) in big-endian order to the buffer 188 | * @param value 189 | * @return the writer 190 | */ 191 | public BufferedByteWriter put32(byte value) 192 | { 193 | return put32(value & 0xFF); 194 | } 195 | 196 | /** 197 | * Write the given int value (32-bit) in big-endian order to the buffer 198 | * @param value 199 | * @return the writer 200 | */ 201 | public BufferedByteWriter put32(short value) 202 | { 203 | return put32(value & 0xFFFF); 204 | } 205 | 206 | /** 207 | * Write the given int value (32-bit) in big-endian order to the buffer 208 | * @param value 209 | * @return the writer 210 | */ 211 | public BufferedByteWriter put32(int value) 212 | { 213 | ensureCapacity(4); 214 | mWriter.putInt(value); 215 | return this; 216 | } 217 | 218 | /** 219 | * Write the given long value (64-bit) in big-endian order to the buffer 220 | * @param value 221 | * @return the writer 222 | */ 223 | public BufferedByteWriter put64(byte value) 224 | { 225 | return put64(value & 0xFFL); 226 | } 227 | 228 | /** 229 | * Write the given long value (64-bit) in big-endian order to the buffer 230 | * @param value 231 | * @return the writer 232 | */ 233 | public BufferedByteWriter put64(short value) 234 | { 235 | return put64(value & 0xFFFFL); 236 | } 237 | 238 | /** 239 | * Write the given long value (64-bit) in big-endian order to the buffer 240 | * @param value 241 | * @return the writer 242 | */ 243 | public BufferedByteWriter put64(int value) 244 | { 245 | return put64(value & 0xFFFFFFFFL); 246 | } 247 | 248 | /** 249 | * Write the given long value (64-bit) in big-endian order to the buffer 250 | * @param value 251 | * @return the writer 252 | */ 253 | public BufferedByteWriter put64(long value) 254 | { 255 | ensureCapacity(8); 256 | mWriter.putLong(value); 257 | return this; 258 | } 259 | 260 | /** 261 | * Convert the internal buffer to a new byte array. 262 | * @return byte array 263 | */ 264 | public byte[] toByteArray() 265 | { 266 | int length = mWriter.position(); 267 | byte[] bytes = new byte[length]; 268 | System.arraycopy(mBuffer, 0, bytes, 0, length); 269 | return bytes; 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/imc/RemediationInstruction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Tobias Brunner 3 | * HSR Hochschule fuer Technik Rapperswil 4 | * 5 | * This program is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License as published by the 7 | * Free Software Foundation; either version 2 of the License, or (at your 8 | * option) any later version. See . 9 | * 10 | * This program is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | */ 15 | 16 | package org.strongswan.android.logic.imc; 17 | 18 | import java.io.IOException; 19 | import java.io.StringReader; 20 | import java.util.Collections; 21 | import java.util.LinkedList; 22 | import java.util.List; 23 | 24 | import org.xmlpull.v1.XmlPullParser; 25 | import org.xmlpull.v1.XmlPullParserException; 26 | 27 | import android.os.Parcel; 28 | import android.os.Parcelable; 29 | import android.util.Xml; 30 | 31 | public class RemediationInstruction implements Parcelable 32 | { 33 | private String mTitle; 34 | private String mDescription; 35 | private String mHeader; 36 | private final List mItems = new LinkedList(); 37 | 38 | @Override 39 | public int describeContents() 40 | { 41 | return 0; 42 | } 43 | 44 | @Override 45 | public void writeToParcel(Parcel dest, int flags) 46 | { 47 | dest.writeString(mTitle); 48 | dest.writeString(mDescription); 49 | dest.writeString(mHeader); 50 | dest.writeStringList(mItems); 51 | } 52 | 53 | public static final Parcelable.Creator CREATOR = new Creator() { 54 | 55 | @Override 56 | public RemediationInstruction[] newArray(int size) 57 | { 58 | return new RemediationInstruction[size]; 59 | } 60 | 61 | @Override 62 | public RemediationInstruction createFromParcel(Parcel source) 63 | { 64 | return new RemediationInstruction(source); 65 | } 66 | }; 67 | 68 | private RemediationInstruction() 69 | { 70 | } 71 | 72 | private RemediationInstruction(Parcel source) 73 | { 74 | mTitle = source.readString(); 75 | mDescription = source.readString(); 76 | mHeader = source.readString(); 77 | source.readStringList(mItems); 78 | } 79 | 80 | public String getTitle() 81 | { 82 | return mTitle; 83 | } 84 | 85 | private void setTitle(String title) 86 | { 87 | mTitle = title; 88 | } 89 | 90 | public String getDescription() 91 | { 92 | return mDescription; 93 | } 94 | 95 | private void setDescription(String description) 96 | { 97 | mDescription = description; 98 | } 99 | 100 | public String getHeader() 101 | { 102 | return mHeader; 103 | } 104 | 105 | private void setHeader(String header) 106 | { 107 | mHeader = header; 108 | } 109 | 110 | public List getItems() 111 | { 112 | return Collections.unmodifiableList(mItems); 113 | } 114 | 115 | private void addItem(String item) 116 | { 117 | mItems.add(item); 118 | } 119 | 120 | /** 121 | * Create a list of RemediationInstruction objects from the given XML data. 122 | * 123 | * @param xml XML data 124 | * @return list of RemediationInstruction objects 125 | */ 126 | public static List fromXml(String xml) 127 | { 128 | List instructions = new LinkedList(); 129 | XmlPullParser parser = Xml.newPullParser(); 130 | try 131 | { 132 | parser.setInput(new StringReader(xml)); 133 | parser.nextTag(); 134 | readInstructions(parser, instructions); 135 | } 136 | catch (XmlPullParserException e) 137 | { 138 | e.printStackTrace(); 139 | } 140 | catch (IOException e) 141 | { 142 | e.printStackTrace(); 143 | } 144 | return instructions; 145 | } 146 | 147 | /** 148 | * Read a <remediationinstructions> element and store the extracted 149 | * RemediationInstruction objects in the given list. 150 | * 151 | * @param parser 152 | * @param instructions 153 | * @throws XmlPullParserException 154 | * @throws IOException 155 | */ 156 | private static void readInstructions(XmlPullParser parser, List instructions) throws XmlPullParserException, IOException 157 | { 158 | parser.require(XmlPullParser.START_TAG, null, "remediationinstructions"); 159 | while (parser.next() != XmlPullParser.END_TAG) 160 | { 161 | if (parser.getEventType() != XmlPullParser.START_TAG) 162 | { 163 | continue; 164 | } 165 | if (parser.getName().equals("instruction")) 166 | { 167 | RemediationInstruction instruction = new RemediationInstruction(); 168 | readInstruction(parser, instruction); 169 | instructions.add(instruction); 170 | } 171 | else 172 | { 173 | skipTag(parser); 174 | } 175 | } 176 | } 177 | 178 | /** 179 | * Read an <instruction> element and store the information in the 180 | * given RemediationInstruction object. 181 | * 182 | * @param parser 183 | * @param instruction 184 | * @throws XmlPullParserException 185 | * @throws IOException 186 | */ 187 | private static void readInstruction(XmlPullParser parser, RemediationInstruction instruction) throws XmlPullParserException, IOException 188 | { 189 | parser.require(XmlPullParser.START_TAG, null, "instruction"); 190 | while (parser.next() != XmlPullParser.END_TAG) 191 | { 192 | if (parser.getEventType() != XmlPullParser.START_TAG) 193 | { 194 | continue; 195 | } 196 | String name = parser.getName(); 197 | if (name.equals("title")) 198 | { 199 | instruction.setTitle(parser.nextText()); 200 | } 201 | else if (name.equals("description")) 202 | { 203 | instruction.setDescription(parser.nextText()); 204 | } 205 | else if (name.equals("itemsheader")) 206 | { 207 | instruction.setHeader(parser.nextText()); 208 | } 209 | else if (name.equals("items")) 210 | { 211 | readItems(parser, instruction); 212 | } 213 | else 214 | { 215 | skipTag(parser); 216 | } 217 | } 218 | } 219 | 220 | /** 221 | * Read all items of an <items> node and add them to the given 222 | * RemediationInstruction object. 223 | * 224 | * @param parser 225 | * @param instruction 226 | * @throws XmlPullParserException 227 | * @throws IOException 228 | */ 229 | private static void readItems(XmlPullParser parser, RemediationInstruction instruction) throws XmlPullParserException, IOException 230 | { 231 | while (parser.next() != XmlPullParser.END_TAG) 232 | { 233 | if (parser.getEventType() != XmlPullParser.START_TAG) 234 | { 235 | continue; 236 | } 237 | if (parser.getName().equals("item")) 238 | { 239 | instruction.addItem(parser.nextText()); 240 | } 241 | else 242 | { 243 | skipTag(parser); 244 | } 245 | } 246 | } 247 | 248 | /** 249 | * Skip the current tag and all child elements. 250 | * 251 | * @param parser 252 | * @throws XmlPullParserException 253 | * @throws IOException 254 | */ 255 | private static void skipTag(XmlPullParser parser) throws XmlPullParserException, IOException 256 | { 257 | int depth = 1; 258 | 259 | parser.require(XmlPullParser.START_TAG, null, null); 260 | while (depth != 0) 261 | { 262 | switch (parser.next()) 263 | { 264 | case XmlPullParser.END_TAG: 265 | depth--; 266 | break; 267 | case XmlPullParser.START_TAG: 268 | depth++; 269 | break; 270 | } 271 | } 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /android/src/main/java/org/strongswan/android/logic/TrustedCertificateManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2015 Tobias Brunner 3 | * Copyright (C) 2012 Giuliano Grassi 4 | * Copyright (C) 2012 Ralf Sager 5 | * HSR Hochschule fuer Technik Rapperswil 6 | * 7 | * This program is free software; you can redistribute it and/or modify it 8 | * under the terms of the GNU General Public License as published by the 9 | * Free Software Foundation; either version 2 of the License, or (at your 10 | * option) any later version. See . 11 | * 12 | * This program is distributed in the hope that it will be useful, but 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 14 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 | * for more details. 16 | */ 17 | 18 | package org.strongswan.android.logic; 19 | 20 | import android.util.Log; 21 | 22 | import java.security.KeyStore; 23 | import java.security.KeyStoreException; 24 | import java.security.cert.Certificate; 25 | import java.security.cert.X509Certificate; 26 | import java.util.ArrayList; 27 | import java.util.Enumeration; 28 | import java.util.Hashtable; 29 | import java.util.Observable; 30 | import java.util.concurrent.locks.ReentrantReadWriteLock; 31 | 32 | public class TrustedCertificateManager extends Observable 33 | { 34 | private static final String TAG = TrustedCertificateManager.class.getSimpleName(); 35 | private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock(); 36 | private Hashtable mCACerts = new Hashtable(); 37 | private volatile boolean mReload; 38 | private boolean mLoaded; 39 | private final ArrayList mKeyStores = new ArrayList(); 40 | 41 | public enum TrustedCertificateSource 42 | { 43 | SYSTEM("system:"), 44 | USER("user:"), 45 | LOCAL("local:"); 46 | 47 | private final String mPrefix; 48 | 49 | private TrustedCertificateSource(String prefix) 50 | { 51 | mPrefix = prefix; 52 | } 53 | 54 | private String getPrefix() 55 | { 56 | return mPrefix; 57 | } 58 | } 59 | 60 | /** 61 | * Private constructor to prevent instantiation from other classes. 62 | */ 63 | private TrustedCertificateManager() 64 | { 65 | for (String name : new String[]{"LocalCertificateStore", "AndroidCAStore"}) 66 | { 67 | KeyStore store; 68 | try 69 | { 70 | store = KeyStore.getInstance(name); 71 | store.load(null, null); 72 | mKeyStores.add(store); 73 | } 74 | catch (Exception e) 75 | { 76 | Log.e(TAG, "Unable to load KeyStore: " + name); 77 | e.printStackTrace(); 78 | } 79 | } 80 | } 81 | 82 | /** 83 | * This is not instantiated until the first call to getInstance() 84 | */ 85 | private static class Singleton 86 | { 87 | public static final TrustedCertificateManager mInstance = new TrustedCertificateManager(); 88 | } 89 | 90 | /** 91 | * Get the single instance of the CA certificate manager. 92 | * 93 | * @return CA certificate manager 94 | */ 95 | public static TrustedCertificateManager getInstance() 96 | { 97 | return Singleton.mInstance; 98 | } 99 | 100 | /** 101 | * Invalidates the current load state so that the next call to load() 102 | * will force a reload of the cached CA certificates. 103 | * 104 | * Observers are notified when this method is called. 105 | * 106 | * @return reference to itself 107 | */ 108 | public TrustedCertificateManager reset() 109 | { 110 | Log.d(TAG, "Force reload of cached CA certificates on next load"); 111 | this.mReload = true; 112 | this.setChanged(); 113 | this.notifyObservers(); 114 | return this; 115 | } 116 | 117 | /** 118 | * Ensures that the certificates are loaded but does not force a reload. 119 | * As this takes a while if the certificates are not loaded yet it should 120 | * be called asynchronously. 121 | * 122 | * Observers are only notified when the certificates are initially loaded, not when reloaded. 123 | * 124 | * @return reference to itself 125 | */ 126 | public TrustedCertificateManager load() 127 | { 128 | Log.d(TAG, "Ensure cached CA certificates are loaded"); 129 | this.mLock.writeLock().lock(); 130 | if (!this.mLoaded || this.mReload) 131 | { 132 | this.mReload = false; 133 | loadCertificates(); 134 | } 135 | this.mLock.writeLock().unlock(); 136 | return this; 137 | } 138 | 139 | /** 140 | * Opens the CA certificate KeyStore and loads the cached certificates. 141 | * The lock must be locked when calling this method. 142 | */ 143 | private void loadCertificates() 144 | { 145 | Log.d(TAG, "Load cached CA certificates"); 146 | Hashtable certs = new Hashtable(); 147 | for (KeyStore store : this.mKeyStores) 148 | { 149 | fetchCertificates(certs, store); 150 | } 151 | this.mCACerts = certs; 152 | if (!this.mLoaded) 153 | { 154 | this.setChanged(); 155 | this.notifyObservers(); 156 | this.mLoaded = true; 157 | } 158 | Log.d(TAG, "Cached CA certificates loaded"); 159 | } 160 | 161 | /** 162 | * Load all X.509 certificates from the given KeyStore. 163 | * 164 | * @param certs Hashtable to store certificates in 165 | * @param store KeyStore to load certificates from 166 | */ 167 | private void fetchCertificates(Hashtable certs, KeyStore store) 168 | { 169 | try 170 | { 171 | Enumeration aliases = store.aliases(); 172 | while (aliases.hasMoreElements()) 173 | { 174 | String alias = aliases.nextElement(); 175 | Certificate cert; 176 | cert = store.getCertificate(alias); 177 | if (cert != null && cert instanceof X509Certificate) 178 | { 179 | certs.put(alias, (X509Certificate)cert); 180 | } 181 | } 182 | } 183 | catch (KeyStoreException ex) 184 | { 185 | ex.printStackTrace(); 186 | } 187 | } 188 | 189 | /** 190 | * Retrieve the CA certificate with the given alias. 191 | * 192 | * @param alias alias of the certificate to get 193 | * @return the certificate, null if not found 194 | */ 195 | public X509Certificate getCACertificateFromAlias(String alias) 196 | { 197 | X509Certificate certificate = null; 198 | 199 | if (this.mLock.readLock().tryLock()) 200 | { 201 | certificate = this.mCACerts.get(alias); 202 | this.mLock.readLock().unlock(); 203 | } 204 | else 205 | { /* if we cannot get the lock load it directly from the KeyStore, 206 | * should be fast for a single certificate */ 207 | for (KeyStore store : this.mKeyStores) 208 | { 209 | try 210 | { 211 | Certificate cert = store.getCertificate(alias); 212 | if (cert != null && cert instanceof X509Certificate) 213 | { 214 | certificate = (X509Certificate)cert; 215 | break; 216 | } 217 | } 218 | catch (KeyStoreException e) 219 | { 220 | e.printStackTrace(); 221 | } 222 | } 223 | } 224 | return certificate; 225 | } 226 | 227 | /** 228 | * Get all CA certificates (from all keystores). 229 | * 230 | * @return Hashtable mapping aliases to certificates 231 | */ 232 | @SuppressWarnings("unchecked") 233 | public Hashtable getAllCACertificates() 234 | { 235 | Hashtable certs; 236 | this.mLock.readLock().lock(); 237 | certs = (Hashtable)this.mCACerts.clone(); 238 | this.mLock.readLock().unlock(); 239 | return certs; 240 | } 241 | 242 | /** 243 | * Get all certificates from the given source. 244 | * 245 | * @param source type to filter certificates 246 | * @return Hashtable mapping aliases to certificates 247 | */ 248 | public Hashtable getCACertificates(TrustedCertificateSource source) 249 | { 250 | Hashtable certs = new Hashtable(); 251 | this.mLock.readLock().lock(); 252 | for (String alias : this.mCACerts.keySet()) 253 | { 254 | if (alias.startsWith(source.getPrefix())) 255 | { 256 | certs.put(alias, this.mCACerts.get(alias)); 257 | } 258 | } 259 | this.mLock.readLock().unlock(); 260 | return certs; 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | --------------------------------------------------------------------------------