├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── vpn ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src ├── androidTest └── java │ └── de │ └── blinkt │ └── openvpn │ └── ExampleInstrumentedTest.java ├── main ├── AndroidManifest.xml ├── assets │ ├── client.ovpn │ ├── nopie_openvpn.arm64-v8a │ ├── nopie_openvpn.armeabi │ ├── nopie_openvpn.armeabi-v7a │ ├── nopie_openvpn.mips │ ├── nopie_openvpn.x86 │ ├── nopie_openvpn.x86_64 │ ├── pie_openvpn.arm64-v8a │ ├── pie_openvpn.armeabi │ ├── pie_openvpn.armeabi-v7a │ ├── pie_openvpn.mips │ ├── pie_openvpn.x86 │ └── pie_openvpn.x86_64 ├── java │ ├── com │ │ └── wxy │ │ │ └── vpn │ │ │ ├── MainActivity.java │ │ │ └── ToyVpnService.java │ ├── de │ │ └── blinkt │ │ │ └── openvpn │ │ │ ├── OpenVpnApi.java │ │ │ ├── VpnProfile.java │ │ │ └── core │ │ │ ├── CIDRIP.java │ │ │ ├── ConfigParser.java │ │ │ ├── Connection.java │ │ │ ├── DeviceStateReceiver.java │ │ │ ├── ICSOpenVPNApplication.java │ │ │ ├── LogFileHandler.java │ │ │ ├── LogItem.java │ │ │ ├── LollipopDeviceStateListener.java │ │ │ ├── NativeUtils.java │ │ │ ├── NetworkSpace.java │ │ │ ├── OpenVPNManagement.java │ │ │ ├── OpenVPNService.java │ │ │ ├── OpenVPNThread.java │ │ │ ├── OpenVpnManagementThread.java │ │ │ ├── PRNGFixes.java │ │ │ ├── ProfileManager.java │ │ │ ├── ProxyDetection.java │ │ │ ├── VPNLaunchHelper.java │ │ │ ├── VpnStatus.java │ │ │ └── X509Utils.java │ └── org │ │ └── spongycastle │ │ └── util │ │ ├── encoders │ │ ├── Base64.java │ │ ├── Base64Encoder.java │ │ └── Encoder.java │ │ └── io │ │ └── pem │ │ ├── PemGenerationException.java │ │ ├── PemHeader.java │ │ ├── PemObject.java │ │ ├── PemObjectGenerator.java │ │ ├── PemReader.java │ │ └── PemWriter.java ├── jniLibs │ ├── arm64-v8a │ │ ├── libjbcrypto.so │ │ ├── libopenvpn.so │ │ └── libopvpnutil.so │ ├── armeabi-v7a │ │ ├── libjbcrypto.so │ │ ├── libopenvpn.so │ │ └── libopvpnutil.so │ ├── armeabi │ │ ├── libjbcrypto.so │ │ ├── libopenvpn.so │ │ └── libopvpnutil.so │ ├── mips │ │ ├── libjbcrypto.so │ │ ├── libopenvpn.so │ │ └── libopvpnutil.so │ ├── x86 │ │ ├── libjbcrypto.so │ │ ├── libopenvpn.so │ │ └── libopvpnutil.so │ └── x86_64 │ │ ├── libjbcrypto.so │ │ ├── libopenvpn.so │ │ └── libopvpnutil.so └── res │ ├── drawable-hdpi │ ├── ic_menu_archive.png │ ├── ic_menu_copy_holo_light.png │ ├── ic_menu_log.png │ ├── ic_stat_vpn.png │ ├── ic_stat_vpn_empty_halo.png │ ├── ic_stat_vpn_offline.png │ ├── ic_stat_vpn_outline.png │ └── vpn_item_settings.png │ ├── layout │ └── activity_main.xml │ ├── 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 │ └── values │ ├── colors.xml │ ├── refs.xml │ ├── strings.xml │ └── styles.xml └── test └── java └── de └── blinkt └── openvpn └── ExampleUnitTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | 11 | .idea 12 | build 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | OpenVPN for Android 2 | ================================= 3 | OpenVpn Android client, simplified from [ics-openvpn](https://github.com/schwabe/ics-openvpn). 4 | 5 | You should prepare your own `client.ovpn` file in the assets folder. 6 | And you may translate the OpenVPN Android client into your native language. 7 | 8 | License 9 | --------------------------------- 10 | [![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-blue.svg)](https://img.shields.io/badge/License-GPL%20v2-blue.svg) 11 | > Make sure you understand the licenses of the code. OpenVPN for Android is GPL licensed. 12 | > You _CANNOT_ build a closed sourced custom UI application without acquiring a different (paid) license for UI code. 13 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 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-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':vpn' -------------------------------------------------------------------------------- /vpn/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /vpn/build.gradle: -------------------------------------------------------------------------------- 1 | //apply plugin: 'com.android.library' 2 | apply plugin: 'com.android.application' 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.wxy.vpn" 9 | minSdkVersion 19 10 | targetSdkVersion 25 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled true 20 | zipAlignEnabled true 21 | shrinkResources true 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | } 26 | 27 | dependencies { 28 | compile fileTree(dir: 'libs', include: ['*.jar']) 29 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 30 | exclude group: 'com.android.support', module: 'support-annotations' 31 | }) 32 | compile 'com.android.support:appcompat-v7:25.1.1' 33 | compile 'com.android.support:design:25.1.1' 34 | testCompile 'junit:junit:4.12' 35 | } 36 | -------------------------------------------------------------------------------- /vpn/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/huangyifei/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /vpn/src/androidTest/java/de/blinkt/openvpn/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package de.blinkt.openvpn; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("de.blinkt.openvpn.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /vpn/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /vpn/src/main/assets/client.ovpn: -------------------------------------------------------------------------------- 1 | client 2 | dev tun 3 | proto udp 4 | sndbuf 0 5 | rcvbuf 0 6 | remote 106.186.120.127 11940 7 | resolv-retry infinite 8 | nobind 9 | persist-key 10 | persist-tun 11 | remote-cert-tls server 12 | cipher AES-128-CBC 13 | comp-lzo 14 | setenv opt block-outside-dns 15 | key-direction 1 16 | verb 3 17 | 18 | -----BEGIN CERTIFICATE----- 19 | MIIDKzCCAhOgAwIBAgIJAJ+kfDNLpoY4MA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV 20 | BAMTCENoYW5nZU1lMB4XDTE2MTExNjEzMDUzOFoXDTI2MTExNDEzMDUzOFowEzER 21 | MA8GA1UEAxMIQ2hhbmdlTWUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB 22 | AQDKMi+gZHV+WJjffc636x4kGeK5d3F23x+Ek8doAYLXktyoQorRW4UYv4+9V2MD 23 | 45yWfJgjuUiGwdomKNl21WApPBbXrOimRHPNICcbyrW7WuSVlf73Sa9SZ6m6itkb 24 | uN8SQyuH6VMYf9bRJONnbbW0awPN8tb4dQsyvqII+Oy4hedV3XuiT38CFLYFVXD4 25 | SXH6Vc9yU0QlGg1vxo3B0xVKV90l4Q7YdeAw6bUvX4Ehb6Dc8iRkQVTpvwVWHsQb 26 | j7baL6mqxzYOGh9jR6a9wjZ3ZOBvEBkjD98qVVjy/wJHZbUxAjX52vwG8PFe/b8t 27 | f9+ZcZJk15S3VN5CiYgC4m+1AgMBAAGjgYEwfzAdBgNVHQ4EFgQU+wovxuG8w2hI 28 | en1QnUgEHMLR9M8wQwYDVR0jBDwwOoAU+wovxuG8w2hIen1QnUgEHMLR9M+hF6QV 29 | MBMxETAPBgNVBAMTCENoYW5nZU1lggkAn6R8M0umhjgwDAYDVR0TBAUwAwEB/zAL 30 | BgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAFCjTCMXHivCBbu5ONE1SbAx 31 | 0z2n3im8pJ9msnubSiR8Q6I85TWtrOWC0QNc0BqsxuyCdwbpSQ2gJmeSDzvn2kdY 32 | isqIx1AOR+HWybfomLiR5P2ZlEchUmv1U6mKh/UQCf+6UUU8WRzsj+PtJTKNsNyT 33 | QEGe+MywGGtQs7kJG2w1M7plPfTSsf0n3gGOBrMugTvyanWa2HW+sMcx7M2t9kom 34 | bT+Y6UmZF4CPUn7PzBrgfR4tTRnYnU9QDuS66Ib+lzSANztvhf2YB2vMotn0vq/5 35 | pNY944L7Sg3CAfkU7LKHhSd+o0cW9dWbnoe9FGt8Pz/aQFlfZ+nwj2fXu06Ig+8= 36 | -----END CERTIFICATE----- 37 | 38 | 39 | -----BEGIN CERTIFICATE----- 40 | MIIDNDCCAhygAwIBAgIBAjANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDaGFu 41 | Z2VNZTAeFw0xNjExMTYxMzA2MDlaFw0yNjExMTQxMzA2MDlaMBExDzANBgNVBAMT 42 | BmNsaWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ7a06FWVfae 43 | zTQ2IsrDuimz5yzKmXtNQePStALXqacYrQTuGaqhn/FIxpdHuXMbudCqrMB+K/zr 44 | 3BrlxyA+CRqfAKF4UZY4YUBLRKsEZJemP41ew17REgdeqf9ojzztywR9YtlDVJGy 45 | 5lnVTjEhZk82WqhDxN5Cplwr54WfFLOvQF8ulagDIhFt5Kkcya7mwaDj3Ns3n/IS 46 | g3kcApnsgnwm1Mvu+FxNmKEaLFS/xW5BmogYFbVQYQe+jXYvUM/XEgzFu4+1Y30r 47 | S9rUgIZpXOMTrVpfoBFnZCHVK2FoM3N1r9ojMOijbYePvIkvYJq1u3CGmtnOPGh/ 48 | EXhUonN0ovsCAwEAAaOBlDCBkTAJBgNVHRMEAjAAMB0GA1UdDgQWBBQxn7TM5Znd 49 | Ff6WERv3p3F2mwFGvTBDBgNVHSMEPDA6gBT7Ci/G4bzDaEh6fVCdSAQcwtH0z6EX 50 | pBUwEzERMA8GA1UEAxMIQ2hhbmdlTWWCCQCfpHwzS6aGODATBgNVHSUEDDAKBggr 51 | BgEFBQcDAjALBgNVHQ8EBAMCB4AwDQYJKoZIhvcNAQELBQADggEBAF9lBliPhzEw 52 | eDM3e/Dyu3ma2ZdB5bLwBY/4+umk/TAYVXkPmM25avJvaAsaNnExJroybV4AR4Ez 53 | J7ZADMzwiWcukEoyo3VZBOiu+NyBKDv1fwvqjj09rZkVd6Duv4scxhpS9JiMY2Qs 54 | KDwpYdXVWOdk5kfDuU8hsJwDw9/HT4tEr7Z9iJLUhyZJJqS9f9qg780j9BRsD118 55 | PuGbPO6znvt/VcYAKNPlSq7QoYsYUWr+Zobhj+OzLVhmbNLSRIHzLHbOsg0MUYRi 56 | 4+o7ZBq+aWpKf+u9lK5d92zAvoPpx4w/rFl0rn7d9nApAoe331vSCNZ/zMgX5I7c 57 | OYT2JZlVCBM= 58 | -----END CERTIFICATE----- 59 | 60 | 61 | -----BEGIN PRIVATE KEY----- 62 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCe2tOhVlX2ns00 63 | NiLKw7ops+csypl7TUHj0rQC16mnGK0E7hmqoZ/xSMaXR7lzG7nQqqzAfiv869wa 64 | 5ccgPgkanwCheFGWOGFAS0SrBGSXpj+NXsNe0RIHXqn/aI887csEfWLZQ1SRsuZZ 65 | 1U4xIWZPNlqoQ8TeQqZcK+eFnxSzr0BfLpWoAyIRbeSpHMmu5sGg49zbN5/yEoN5 66 | HAKZ7IJ8JtTL7vhcTZihGixUv8VuQZqIGBW1UGEHvo12L1DP1xIMxbuPtWN9K0va 67 | 1ICGaVzjE61aX6ARZ2Qh1SthaDNzda/aIzDoo22Hj7yJL2CatbtwhprZzjxofxF4 68 | VKJzdKL7AgMBAAECggEAVg7Wd6D+EQ0WviJV1uMSB2We53UakOHZwEJ1otSS5CNK 69 | GFGlNfs5Ws3CSFJi9SkgERtlWyL/UZW1OB0jMyRoajOZc+Bq0qbYiKw+FyfaFw3J 70 | gBzGWqeIzNVL40IR10A7q8MBuhHvzr7Ma/5Szer6Mg+QLXgJPx98YJKoaosz++Mq 71 | ruKrHeTR8AtZw5xZtkgJmKotOaDk9JCs79/uLNHt1ZFEpZ4huzemjhTlmC8LAPsN 72 | GpYxKmPFp5Pmpc9gPT23EpkDlOmK5D+C6cQP0ydc2+gt9/elieBF8LqOfQceTNsn 73 | LWra5He/ea8/IQ+Inbn+fqMyytRci0zruYv/wimzAQKBgQDQYFVfX//VzyjpX1ds 74 | cpKQHKHOrQR0JK7Ix+vLq8O4UgO3jiTut1j81zC0KOK+XEM8ndsAdl51qjurIbBN 75 | I1fwDcW8kCmHxNEUQKyx6asBTi/3gOk2BL39/Yti3gmGCtLQfN/fGdgkNTDsaEGk 76 | aJulfmN/bD6VWa7QB/p3YU+oSQKBgQDDKReNucP2Y/nsETJn1JngSwI2/jh8+0Pg 77 | h1UlavxpbsmD++T7Cmk7amuxTJghX7UfWGP+DYg+Hf2IRggkdmdzjvpMbLRahlLI 78 | NLKGVyAS4GsyGVXmDu1aBV2/WC6aWC+Xbb7u8WKerud1ra+2j/32nnvQdBXjDSFO 79 | 4+UA3BWZIwKBgQC7R7mzTL7NVbE5fn0+dQMRDwrGjG5STJ3iyvw8Q5ZW1S+OC+1Z 80 | t+P86xNU+OJBoP61PlMU6U4ghu2180NXMQraMCamQRNyeQxJHY+ZH6CYPcOSuYPh 81 | DwYLf5OFOnZoyNWMmI9FHx9J+5yK/XDx6Srq5NtcLNdauA4L3v/Xx9gmMQKBgFgy 82 | z/Q7CfbmQ+KN+wu1pz0YaQ3VYk4JeCmWv8JDUYOMkAHX6yoREHHLrJPaG76pdD+Q 83 | QNBl6Ta5cwUNMUzZPfOZoEC9t9xZbDwSpMPVyCb/yTJBBPMNt/LBDhcVkSorxIZZ 84 | ELsH2BwCy5QBRLHq7UlxYl9YtzN/kZgHokz7Kb+lAoGBALHiaXZbazp8z8z0w3SG 85 | koCtzYdsMrMsX7WrUo7JkxJ0ppF/oDzAeV76kprTdocVTI+2iDEFFBEahnCzS/bA 86 | r7bSmCPo7Rw9zMMlvs2Qi+GiWE1wgej300IBIYYNzK18giuiswSNc5gFgJD/+PLn 87 | qJMk36XqcIuvRvikk36c3FFo 88 | -----END PRIVATE KEY----- 89 | 90 | 91 | # 92 | # 2048 bit OpenVPN static key 93 | # 94 | -----BEGIN OpenVPN Static key V1----- 95 | 785cc328b40b7b64ee79be157320b1eb 96 | 553b25b335dcbed31edbae620d5518d2 97 | 7c2849ec98b98830baecd5bbe6114702 98 | 7f1cea34962166bfa49f96ce4392c7ab 99 | 0d436fd1b8facc01215a1541fdea9548 100 | df5b5896b48e952c9d398a9072ef5797 101 | 482e5e411e9d0693477fd67a47ce2d89 102 | cf9d04d93a7742c6fd444a51ca8dbb49 103 | 5abec6382b73a892e6a85fa2ba5afe48 104 | 2ad125c9d7ccda2b4fb32ddcb86b8980 105 | d4094897f686314d45b2800d899eab60 106 | be0049c143449aaf69930af86c318bdb 107 | 67450fc6df7cf2fc6067d5f7a7b055ee 108 | 91ddcbb2559fdc9820141b77db2cf649 109 | 66a6c39ca6401332b80c4136238267f1 110 | 990c8d5814783bd418b48aa34e9c52bd 111 | -----END OpenVPN Static key V1----- 112 | 113 | -------------------------------------------------------------------------------- /vpn/src/main/assets/nopie_openvpn.arm64-v8a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/assets/nopie_openvpn.arm64-v8a -------------------------------------------------------------------------------- /vpn/src/main/assets/nopie_openvpn.armeabi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/assets/nopie_openvpn.armeabi -------------------------------------------------------------------------------- /vpn/src/main/assets/nopie_openvpn.armeabi-v7a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/assets/nopie_openvpn.armeabi-v7a -------------------------------------------------------------------------------- /vpn/src/main/assets/nopie_openvpn.mips: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/assets/nopie_openvpn.mips -------------------------------------------------------------------------------- /vpn/src/main/assets/nopie_openvpn.x86: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/assets/nopie_openvpn.x86 -------------------------------------------------------------------------------- /vpn/src/main/assets/nopie_openvpn.x86_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/assets/nopie_openvpn.x86_64 -------------------------------------------------------------------------------- /vpn/src/main/assets/pie_openvpn.arm64-v8a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/assets/pie_openvpn.arm64-v8a -------------------------------------------------------------------------------- /vpn/src/main/assets/pie_openvpn.armeabi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/assets/pie_openvpn.armeabi -------------------------------------------------------------------------------- /vpn/src/main/assets/pie_openvpn.armeabi-v7a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/assets/pie_openvpn.armeabi-v7a -------------------------------------------------------------------------------- /vpn/src/main/assets/pie_openvpn.mips: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/assets/pie_openvpn.mips -------------------------------------------------------------------------------- /vpn/src/main/assets/pie_openvpn.x86: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/assets/pie_openvpn.x86 -------------------------------------------------------------------------------- /vpn/src/main/assets/pie_openvpn.x86_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/assets/pie_openvpn.x86_64 -------------------------------------------------------------------------------- /vpn/src/main/java/com/wxy/vpn/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.wxy.vpn; 2 | 3 | import android.content.ComponentName; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.ServiceConnection; 7 | import android.os.Bundle; 8 | import android.os.IBinder; 9 | import android.os.RemoteException; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.support.v7.widget.SwitchCompat; 12 | import android.view.View; 13 | import android.widget.CompoundButton; 14 | 15 | import java.io.BufferedReader; 16 | import java.io.IOException; 17 | import java.io.InputStream; 18 | import java.io.InputStreamReader; 19 | 20 | import de.blinkt.openvpn.OpenVpnApi; 21 | import de.blinkt.openvpn.R; 22 | import de.blinkt.openvpn.core.OpenVPNService; 23 | import de.blinkt.openvpn.core.ProfileManager; 24 | import de.blinkt.openvpn.core.VpnStatus; 25 | 26 | public class MainActivity extends AppCompatActivity { 27 | private SwitchCompat scOpenVpn; 28 | private boolean isVpnStarted; 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_main); 33 | VpnStatus.initLogCache(getApplicationContext().getCacheDir()); 34 | Intent intent = new Intent(this, OpenVPNService.class); 35 | intent.setAction(OpenVPNService.START_SERVICE); 36 | bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 37 | 38 | scOpenVpn = (SwitchCompat) findViewById(R.id.sc_vpn); 39 | scOpenVpn.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 40 | @Override 41 | public void onCheckedChanged(CompoundButton compoundButton, boolean b) { 42 | if (b) { 43 | startVpn(); 44 | isVpnStarted = true; 45 | } else { 46 | closeVpn(); 47 | isVpnStarted = false; 48 | } 49 | } 50 | }); 51 | } 52 | 53 | private void startVpn() { 54 | try { 55 | InputStream conf = getAssets().open("client.ovpn");// your own file in /assets/client.ovpn 56 | InputStreamReader isr = new InputStreamReader(conf); 57 | BufferedReader br = new BufferedReader(isr); 58 | String config = ""; 59 | String line; 60 | while (true) { 61 | line = br.readLine(); 62 | if (line == null) break; 63 | config += line + "\n"; 64 | } 65 | br.readLine(); 66 | OpenVpnApi.startVpn(this, config, null, null); 67 | } catch (IOException | RemoteException e) { 68 | e.printStackTrace(); 69 | } 70 | } 71 | 72 | //============== 73 | protected OpenVPNService mService; 74 | private ServiceConnection mConnection = new ServiceConnection() { 75 | @Override 76 | public void onServiceConnected(ComponentName className, IBinder service) { 77 | // We've bound to LocalService, cast the IBinder and get LocalService instance 78 | OpenVPNService.LocalBinder binder = (OpenVPNService.LocalBinder) service; 79 | mService = binder.getService(); 80 | } 81 | 82 | @Override 83 | public void onServiceDisconnected(ComponentName arg0) { 84 | mService = null; 85 | } 86 | }; 87 | 88 | @Override 89 | protected void onDestroy() { 90 | super.onDestroy(); 91 | unbindService(mConnection); 92 | } 93 | 94 | private void closeVpn() { 95 | ProfileManager.setConntectedVpnProfileDisconnected(this); 96 | if (mService != null && mService.getManagement() != null) { 97 | mService.getManagement().stopVPN(false); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /vpn/src/main/java/com/wxy/vpn/ToyVpnService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.wxy.vpn; 17 | 18 | import android.app.PendingIntent; 19 | import android.content.Intent; 20 | import android.net.VpnService; 21 | import android.os.Handler; 22 | import android.os.Message; 23 | import android.os.ParcelFileDescriptor; 24 | import android.util.Log; 25 | import android.widget.Toast; 26 | 27 | import java.io.FileInputStream; 28 | import java.io.FileOutputStream; 29 | import java.net.InetSocketAddress; 30 | import java.nio.ByteBuffer; 31 | import java.nio.channels.DatagramChannel; 32 | 33 | import de.blinkt.openvpn.R; 34 | 35 | 36 | public class ToyVpnService extends VpnService implements Handler.Callback, Runnable { 37 | private static final String TAG = "ToyVpnService"; 38 | private String mServerAddress; 39 | private String mServerPort; 40 | private byte[] mSharedSecret; 41 | private PendingIntent mConfigureIntent; 42 | private Handler mHandler; 43 | private Thread mThread; 44 | private ParcelFileDescriptor mInterface; 45 | private String mParameters; 46 | private boolean isStopped = false; 47 | 48 | @Override 49 | public int onStartCommand(Intent intent, int flags, int startId) { 50 | Log.d(TAG, "onStartCommand: " + intent.getExtras()); 51 | if (intent.getBooleanExtra("stop", false)) { 52 | isStopped = true; 53 | stopSelf(); 54 | return START_NOT_STICKY; 55 | } 56 | // The handler is only used to show messages. 57 | if (mHandler == null) { 58 | mHandler = new Handler(this); 59 | } 60 | // Stop the previous session by interrupting the thread. 61 | if (mThread != null) { 62 | mThread.interrupt(); 63 | } 64 | isStopped = false; 65 | // Extract information from the intent. 66 | String prefix = getPackageName(); 67 | mServerAddress = intent.getStringExtra(prefix + ".ADDRESS"); 68 | mServerPort = intent.getStringExtra(prefix + ".PORT"); 69 | mSharedSecret = intent.getStringExtra(prefix + ".SECRET").getBytes(); 70 | // Start a new session by creating a new thread. 71 | mThread = new Thread(this, "ToyVpnThread"); 72 | mThread.start(); 73 | return START_STICKY; 74 | } 75 | 76 | @Override 77 | public void onDestroy() { 78 | super.onDestroy(); 79 | Log.d(TAG, "onDestroy: "); 80 | if (mThread != null) { 81 | try { 82 | mThread.join(); 83 | } catch (InterruptedException e) { 84 | e.printStackTrace(); 85 | } 86 | mThread = null; 87 | } 88 | } 89 | 90 | @Override 91 | public boolean handleMessage(Message message) { 92 | if (message != null) { 93 | Toast.makeText(this, message.what, Toast.LENGTH_SHORT).show(); 94 | } 95 | return true; 96 | } 97 | 98 | @Override 99 | public synchronized void run() { 100 | try { 101 | Log.i(TAG, "Starting"); 102 | // If anything needs to be obtained using the network, get it now. 103 | // This greatly reduces the complexity of seamless handover, which 104 | // tries to recreate the tunnel without shutting down everything. 105 | // In this demo, all we need to know is the server address. 106 | InetSocketAddress server = new InetSocketAddress(mServerAddress, Integer.parseInt(mServerPort)); 107 | // We try to create the tunnel for several times. The better way 108 | // is to work with ConnectivityManager, such as trying only when 109 | // the network is avaiable. Here we just use a counter to keep 110 | // things simple. 111 | for (int attempt = 0; attempt < 10; ++attempt) { 112 | if (isStopped) { 113 | break; 114 | } 115 | mHandler.sendEmptyMessage(R.string.connecting); 116 | // Reset the counter if we were connected. 117 | if (run(server)) { 118 | attempt = 0; 119 | } 120 | // Sleep for a while. This also checks if we got interrupted. 121 | Thread.sleep(3000); 122 | } 123 | Log.i(TAG, "Giving up"); 124 | } catch (Exception e) { 125 | Log.e(TAG, "Got " + e.toString()); 126 | } finally { 127 | try { 128 | mInterface.close(); 129 | } catch (Exception e) { 130 | // ignore 131 | } 132 | mInterface = null; 133 | mParameters = null; 134 | mHandler.sendEmptyMessage(R.string.disconnected); 135 | Log.i(TAG, "Exiting"); 136 | } 137 | } 138 | 139 | private boolean run(InetSocketAddress server) throws Exception { 140 | DatagramChannel tunnel = null; 141 | boolean connected = false; 142 | try { 143 | // Create a DatagramChannel as the VPN tunnel. 144 | tunnel = DatagramChannel.open(); 145 | // Protect the tunnel before connecting to avoid loopback. 146 | if (!protect(tunnel.socket())) { 147 | throw new IllegalStateException("Cannot protect the tunnel"); 148 | } 149 | // Connect to the server. 150 | tunnel.connect(server); 151 | // For simplicity, we use the same thread for both reading and 152 | // writing. Here we put the tunnel into non-blocking mode. 153 | tunnel.configureBlocking(false); 154 | // Authenticate and configure the virtual network interface. 155 | handshake(tunnel); 156 | // Now we are connected. Set the flag and show the message. 157 | connected = true; 158 | mHandler.sendEmptyMessage(R.string.connected); 159 | // Packets to be sent are queued in this input stream. 160 | FileInputStream in = new FileInputStream(mInterface.getFileDescriptor()); 161 | // Packets received need to be written to this output stream. 162 | FileOutputStream out = new FileOutputStream(mInterface.getFileDescriptor()); 163 | // Allocate the buffer for a single packet. 164 | ByteBuffer packet = ByteBuffer.allocate(32767); 165 | // We use a timer to determine the status of the tunnel. It 166 | // works on both sides. A positive value means sending, and 167 | // any other means receiving. We start with receiving. 168 | int timer = 0; 169 | // We keep forwarding packets till something goes wrong. 170 | while (!isStopped) { 171 | // Assume that we did not make any progress in this iteration. 172 | boolean idle = true; 173 | // Read the outgoing packet from the input stream. 174 | int length = in.read(packet.array()); 175 | if (length > 0) Log.d(TAG, "run: outgoing:" + length); 176 | if (length > 0) { 177 | // Write the outgoing packet to the tunnel. 178 | packet.limit(length); 179 | tunnel.write(packet); 180 | packet.clear(); 181 | // There might be more outgoing packets. 182 | idle = false; 183 | // If we were receiving, switch to sending. 184 | if (timer < 1) { 185 | timer = 1; 186 | } 187 | } 188 | // Read the incoming packet from the tunnel. 189 | length = tunnel.read(packet); 190 | if (length > 0) Log.d(TAG, "run: incoming:" + length); 191 | if (length > 0) { 192 | // Ignore control messages, which start with zero. 193 | if (packet.get(0) != 0) { 194 | // Write the incoming packet to the output stream. 195 | out.write(packet.array(), 0, length); 196 | } 197 | packet.clear(); 198 | // There might be more incoming packets. 199 | idle = false; 200 | // If we were sending, switch to receiving. 201 | if (timer > 0) { 202 | timer = 0; 203 | } 204 | } 205 | // If we are idle or waiting for the network, sleep for a 206 | // fraction of time to avoid busy looping. 207 | if (idle) { 208 | Thread.sleep(100); 209 | // Increase the timer. This is inaccurate but good enough, 210 | // since everything is operated in non-blocking mode. 211 | timer += (timer > 0) ? 100 : -100; 212 | // We are receiving for a long time but not sending. 213 | if (timer < -15000) { 214 | // Send empty control messages. 215 | packet.put((byte) 0).limit(1); 216 | for (int i = 0; i < 3; ++i) { 217 | packet.position(0); 218 | tunnel.write(packet); 219 | } 220 | packet.clear(); 221 | // Switch to sending. 222 | timer = 1; 223 | } 224 | // We are sending for a long time but not receiving. 225 | if (timer > 20000) { 226 | throw new IllegalStateException("Timed out"); 227 | } 228 | } 229 | } 230 | } catch (InterruptedException e) { 231 | throw e; 232 | } catch (Exception e) { 233 | Log.e(TAG, "Got " + e.toString()); 234 | } finally { 235 | try { 236 | tunnel.close(); 237 | } catch (Exception e) { 238 | // ignore 239 | } 240 | } 241 | return connected; 242 | } 243 | 244 | private void handshake(DatagramChannel tunnel) throws Exception { 245 | // To build a secured tunnel, we should perform mutual authentication 246 | // and exchange session keys for encryption. To keep things simple in 247 | // this demo, we just send the shared secret in plaintext and wait 248 | // for the server to send the parameters. 249 | // Allocate the buffer for handshaking. 250 | ByteBuffer packet = ByteBuffer.allocate(1024); 251 | // Control messages always start with zero. 252 | packet.put((byte) 0).put(mSharedSecret).flip(); 253 | // Send the secret several times in case of packet loss. 254 | for (int i = 0; i < 3; ++i) { 255 | packet.position(0); 256 | tunnel.write(packet); 257 | } 258 | packet.clear(); 259 | // Wait for the parameters within a limited time. 260 | for (int i = 0; i < 50; ++i) { 261 | Thread.sleep(100); 262 | // Normally we should not receive random packets. 263 | int length = tunnel.read(packet); 264 | if (length > 0 && packet.get(0) == 0) { 265 | configure(new String(packet.array(), 1, length - 1).trim()); 266 | return; 267 | } 268 | } 269 | throw new IllegalStateException("Timed out"); 270 | } 271 | 272 | private void configure(String parameters) throws Exception { 273 | // If the old interface has exactly the same parameters, use it! 274 | if (mInterface != null && parameters.equals(mParameters)) { 275 | Log.i(TAG, "Using the previous interface"); 276 | return; 277 | } 278 | // Configure a builder while parsing the parameters. 279 | Builder builder = new Builder(); 280 | Log.d(TAG, "configure: " + parameters); 281 | for (String parameter : parameters.split(" ")) { 282 | String[] fields = parameter.split(","); 283 | try { 284 | switch (fields[0].charAt(0)) { 285 | case 'm': 286 | builder.setMtu(Short.parseShort(fields[1])); 287 | break; 288 | case 'a': 289 | builder.addAddress(fields[1], Integer.parseInt(fields[2])); 290 | break; 291 | case 'r': 292 | builder.addRoute(fields[1], Integer.parseInt(fields[2])); 293 | break; 294 | case 'd': 295 | builder.addDnsServer(fields[1]); 296 | break; 297 | case 's': 298 | builder.addSearchDomain(fields[1]); 299 | break; 300 | } 301 | } catch (Exception e) { 302 | throw new IllegalArgumentException("Bad parameter: " + parameter); 303 | } 304 | } 305 | // Close the old interface since the parameters have been changed. 306 | try { 307 | mInterface.close(); 308 | } catch (Exception e) { 309 | // ignore 310 | } 311 | // Create a new interface using the builder and save the parameters. 312 | mInterface = builder.setSession(mServerAddress).setConfigureIntent(mConfigureIntent).establish(); 313 | mParameters = parameters; 314 | Log.i(TAG, "New interface: " + parameters); 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /vpn/src/main/java/de/blinkt/openvpn/OpenVpnApi.java: -------------------------------------------------------------------------------- 1 | package de.blinkt.openvpn; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.net.VpnService; 6 | import android.os.Build; 7 | import android.os.RemoteException; 8 | import android.text.TextUtils; 9 | import android.util.Log; 10 | 11 | import java.io.IOException; 12 | import java.io.StringReader; 13 | 14 | import de.blinkt.openvpn.core.ConfigParser; 15 | import de.blinkt.openvpn.core.ProfileManager; 16 | import de.blinkt.openvpn.core.VPNLaunchHelper; 17 | 18 | public class OpenVpnApi { 19 | /** 20 | * @param inlineConfig 一般都保存在文件中,通过读取文件获取配置信息 21 | * @param userName 某些ovpn的连接方式需要用户名和密码,可以为空 22 | * @param pw 某些ovpn的连接方式需要用户名和密码,可以为空 23 | * @throws RemoteException 24 | */ 25 | public static void startVpn(Context context, String inlineConfig, String userName, String pw) throws RemoteException { 26 | if (TextUtils.isEmpty(inlineConfig)) throw new RemoteException("config is empty"); 27 | VpnService.prepare(context); 28 | startVpnInternal(context, inlineConfig, userName, pw); 29 | } 30 | 31 | private static void startVpnInternal(Context context, String inlineConfig, String userName, String pw) throws RemoteException { 32 | ConfigParser cp = new ConfigParser(); 33 | try { 34 | cp.parseConfig(new StringReader(inlineConfig)); 35 | VpnProfile vp = cp.convertProfile();// 解析.ovpn 36 | vp.mName = Build.MODEL; 37 | if (vp.checkProfile(context) != de.blinkt.openvpn.R.string.no_error_found) { 38 | throw new RemoteException(context.getString(vp.checkProfile(context))); 39 | } 40 | vp.mProfileCreator = context.getPackageName(); 41 | vp.mUsername = userName; 42 | vp.mPassword = pw; 43 | ProfileManager.setTemporaryProfile(vp); 44 | VPNLaunchHelper.startOpenVpn(vp, context);// 开启Vpn服务 45 | } catch (IOException | ConfigParser.ConfigParseError e) { 46 | throw new RemoteException(e.getMessage()); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /vpn/src/main/java/de/blinkt/openvpn/core/CIDRIP.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | package de.blinkt.openvpn.core; 6 | 7 | import java.util.Locale; 8 | 9 | class CIDRIP { 10 | String mIp; 11 | int len; 12 | 13 | public CIDRIP(String ip, String mask) { 14 | mIp = ip; 15 | long netmask = getInt(mask); 16 | // Add 33. bit to ensure the loop terminates 17 | netmask += 1l << 32; 18 | int lenZeros = 0; 19 | while ((netmask & 0x1) == 0) { 20 | lenZeros++; 21 | netmask = netmask >> 1; 22 | } 23 | // Check if rest of netmask is only 1s 24 | if (netmask != (0x1ffffffffl >> lenZeros)) { 25 | // Asume no CIDR, set /32 26 | len = 32; 27 | } else { 28 | len = 32 - lenZeros; 29 | } 30 | } 31 | 32 | public CIDRIP(String address, int prefix_length) { 33 | len = prefix_length; 34 | mIp = address; 35 | } 36 | 37 | static long getInt(String ipaddr) { 38 | String[] ipt = ipaddr.split("\\."); 39 | long ip = 0; 40 | ip += Long.parseLong(ipt[0]) << 24; 41 | ip += Integer.parseInt(ipt[1]) << 16; 42 | ip += Integer.parseInt(ipt[2]) << 8; 43 | ip += Integer.parseInt(ipt[3]); 44 | return ip; 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return String.format(Locale.ENGLISH, "%s/%d", mIp, len); 50 | } 51 | 52 | public boolean normalise() { 53 | long ip = getInt(mIp); 54 | long newip = ip & (0xffffffffl << (32 - len)); 55 | if (newip != ip) { 56 | mIp = String.format("%d.%d.%d.%d", (newip & 0xff000000) >> 24, (newip & 0xff0000) >> 16, (newip & 0xff00) >> 8, newip & 0xff); 57 | return true; 58 | } else { 59 | return false; 60 | } 61 | } 62 | 63 | public long getInt() { 64 | return getInt(mIp); 65 | } 66 | } -------------------------------------------------------------------------------- /vpn/src/main/java/de/blinkt/openvpn/core/Connection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | package de.blinkt.openvpn.core; 6 | 7 | import android.text.TextUtils; 8 | 9 | import java.io.Serializable; 10 | 11 | public class Connection implements Serializable, Cloneable { 12 | public static final int CONNECTION_DEFAULT_TIMEOUT = 120; 13 | private static final long serialVersionUID = 92031902903829089L; 14 | public String mServerName = "openvpn.blinkt.de"; 15 | public String mServerPort = "1194"; 16 | public boolean mUseUdp = true; 17 | public String mCustomConfiguration = ""; 18 | public boolean mUseCustomConfig = false; 19 | public boolean mEnabled = true; 20 | public int mConnectTimeout = 0; 21 | 22 | public String getConnectionBlock() { 23 | String cfg = ""; 24 | // Server Address 25 | cfg += "remote "; 26 | cfg += mServerName; 27 | cfg += " "; 28 | cfg += mServerPort; 29 | if (mUseUdp) cfg += " udp\n"; 30 | else cfg += " tcp-client\n"; 31 | if (mConnectTimeout != 0) cfg += String.format(" connect-timeout %d\n", mConnectTimeout); 32 | if (!TextUtils.isEmpty(mCustomConfiguration) && mUseCustomConfig) { 33 | cfg += mCustomConfiguration; 34 | cfg += "\n"; 35 | } 36 | return cfg; 37 | } 38 | 39 | @Override 40 | public Connection clone() throws CloneNotSupportedException { 41 | return (Connection) super.clone(); 42 | } 43 | 44 | public boolean isOnlyRemote() { 45 | return TextUtils.isEmpty(mCustomConfiguration) || !mUseCustomConfig; 46 | } 47 | 48 | public int getTimeout() { 49 | if (mConnectTimeout <= 0) return CONNECTION_DEFAULT_TIMEOUT; 50 | else return mConnectTimeout; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /vpn/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | package de.blinkt.openvpn.core; 6 | 7 | import android.content.BroadcastReceiver; 8 | import android.content.Context; 9 | import android.content.Intent; 10 | import android.content.SharedPreferences; 11 | import android.net.ConnectivityManager; 12 | import android.net.NetworkInfo; 13 | import android.net.NetworkInfo.State; 14 | import android.os.Handler; 15 | import android.preference.PreferenceManager; 16 | 17 | import java.util.LinkedList; 18 | 19 | import de.blinkt.openvpn.R; 20 | import de.blinkt.openvpn.core.VpnStatus.ByteCountListener; 21 | 22 | import static de.blinkt.openvpn.core.OpenVPNManagement.pauseReason; 23 | 24 | public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountListener, OpenVPNManagement.PausedStateCallback { 25 | private final Handler mDisconnectHandler; 26 | // Window time in s 27 | private final int TRAFFIC_WINDOW = 60; 28 | // Data traffic limit in bytes 29 | private final long TRAFFIC_LIMIT = 64 * 1024; 30 | // Time to wait after network disconnect to pause the VPN 31 | private final int DISCONNECT_WAIT = 20; 32 | connectState network = connectState.DISCONNECTED; 33 | connectState screen = connectState.SHOULDBECONNECTED; 34 | connectState userpause = connectState.SHOULDBECONNECTED; 35 | LinkedList trafficdata = new LinkedList(); 36 | private int lastNetwork = -1; 37 | private OpenVPNManagement mManagement; 38 | private String lastStateMsg = null; 39 | private Runnable mDelayDisconnectRunnable = new Runnable() { 40 | @Override 41 | public void run() { 42 | if (!(network == connectState.PENDINGDISCONNECT)) return; 43 | network = connectState.DISCONNECTED; 44 | // Set screen state to be disconnected if disconnect pending 45 | if (screen == connectState.PENDINGDISCONNECT) screen = connectState.DISCONNECTED; 46 | mManagement.pause(getPauseReason()); 47 | } 48 | }; 49 | private NetworkInfo lastConnectedNetwork; 50 | 51 | public DeviceStateReceiver(OpenVPNManagement magnagement) { 52 | super(); 53 | mManagement = magnagement; 54 | mManagement.setPauseCallback(this); 55 | mDisconnectHandler = new Handler(); 56 | } 57 | 58 | public static boolean equalsObj(Object a, Object b) { 59 | return (a == null) ? (b == null) : a.equals(b); 60 | } 61 | 62 | @Override 63 | public boolean shouldBeRunning() { 64 | return shouldBeConnected(); 65 | } 66 | 67 | @Override 68 | public void updateByteCount(long in, long out, long diffIn, long diffOut) { 69 | if (screen != connectState.PENDINGDISCONNECT) return; 70 | long total = diffIn + diffOut; 71 | trafficdata.add(new Datapoint(System.currentTimeMillis(), total)); 72 | while (trafficdata.getFirst().timestamp <= (System.currentTimeMillis() - TRAFFIC_WINDOW * 1000)) { 73 | trafficdata.removeFirst(); 74 | } 75 | long windowtraffic = 0; 76 | for (Datapoint dp : trafficdata) 77 | windowtraffic += dp.data; 78 | if (windowtraffic < TRAFFIC_LIMIT) { 79 | screen = connectState.DISCONNECTED; 80 | VpnStatus.logInfo(R.string.screenoff_pause, OpenVPNService.humanReadableByteCount(TRAFFIC_LIMIT, false), TRAFFIC_WINDOW); 81 | mManagement.pause(getPauseReason()); 82 | } 83 | } 84 | 85 | public void userPause(boolean pause) { 86 | if (pause) { 87 | userpause = connectState.DISCONNECTED; 88 | // Check if we should disconnect 89 | mManagement.pause(getPauseReason()); 90 | } else { 91 | boolean wereConnected = shouldBeConnected(); 92 | userpause = connectState.SHOULDBECONNECTED; 93 | if (shouldBeConnected() && !wereConnected) mManagement.resume(); 94 | else 95 | // Update the reason why we currently paused 96 | mManagement.pause(getPauseReason()); 97 | } 98 | } 99 | 100 | @Override 101 | public void onReceive(Context context, Intent intent) { 102 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 103 | if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { 104 | networkStateChange(context); 105 | } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { 106 | boolean screenOffPause = prefs.getBoolean("screenoff", false); 107 | if (screenOffPause) { 108 | if (ProfileManager.getLastConnectedVpn() != null && !ProfileManager.getLastConnectedVpn().mPersistTun) VpnStatus.logError(R.string.screen_nopersistenttun); 109 | screen = connectState.PENDINGDISCONNECT; 110 | fillTrafficData(); 111 | if (network == connectState.DISCONNECTED || userpause == connectState.DISCONNECTED) screen = connectState.DISCONNECTED; 112 | } 113 | } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { 114 | // Network was disabled because screen off 115 | boolean connected = shouldBeConnected(); 116 | screen = connectState.SHOULDBECONNECTED; 117 | /* We should connect now, cancel any outstanding disconnect timer */ 118 | mDisconnectHandler.removeCallbacks(mDelayDisconnectRunnable); 119 | /* should be connected has changed because the screen is on now, connect the VPN */ 120 | if (shouldBeConnected() != connected) mManagement.resume(); 121 | else if (!shouldBeConnected()) 122 | /*Update the reason why we are still paused */ mManagement.pause(getPauseReason()); 123 | } 124 | } 125 | 126 | private void fillTrafficData() { 127 | trafficdata.add(new Datapoint(System.currentTimeMillis(), TRAFFIC_LIMIT)); 128 | } 129 | 130 | public void networkStateChange(Context context) { 131 | NetworkInfo networkInfo = getCurrentNetworkInfo(context); 132 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 133 | boolean sendusr1 = prefs.getBoolean("netchangereconnect", true); 134 | String netstatestring; 135 | if (networkInfo == null) { 136 | netstatestring = "not connected"; 137 | } else { 138 | String subtype = networkInfo.getSubtypeName(); 139 | if (subtype == null) subtype = ""; 140 | String extrainfo = networkInfo.getExtraInfo(); 141 | if (extrainfo == null) extrainfo = ""; 142 | /* 143 | if(networkInfo.getType()==android.net.ConnectivityManager.TYPE_WIFI) { 144 | WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 145 | WifiInfo wifiinfo = wifiMgr.getConnectionInfo(); 146 | extrainfo+=wifiinfo.getBSSID(); 147 | subtype += wifiinfo.getNetworkId(); 148 | }*/ 149 | netstatestring = String.format("%2$s %4$s to %1$s %3$s", networkInfo.getTypeName(), networkInfo.getDetailedState(), extrainfo, subtype); 150 | } 151 | if (networkInfo != null && networkInfo.getState() == State.CONNECTED) { 152 | int newnet = networkInfo.getType(); 153 | boolean pendingDisconnect = (network == connectState.PENDINGDISCONNECT); 154 | network = connectState.SHOULDBECONNECTED; 155 | boolean sameNetwork; 156 | if (lastConnectedNetwork == null || lastConnectedNetwork.getType() != networkInfo.getType() || !equalsObj(lastConnectedNetwork.getExtraInfo(), networkInfo.getExtraInfo())) 157 | sameNetwork = false; 158 | else sameNetwork = true; 159 | /* Same network, connection still 'established' */ 160 | if (pendingDisconnect && sameNetwork) { 161 | mDisconnectHandler.removeCallbacks(mDelayDisconnectRunnable); 162 | // Reprotect the sockets just be sure 163 | mManagement.networkChange(true); 164 | } else { 165 | /* Different network or connection not established anymore */ 166 | if (screen == connectState.PENDINGDISCONNECT) screen = connectState.DISCONNECTED; 167 | if (shouldBeConnected()) { 168 | mDisconnectHandler.removeCallbacks(mDelayDisconnectRunnable); 169 | if (pendingDisconnect || !sameNetwork) mManagement.networkChange(sameNetwork); 170 | else mManagement.resume(); 171 | } 172 | lastNetwork = newnet; 173 | lastConnectedNetwork = networkInfo; 174 | } 175 | } else if (networkInfo == null) { 176 | // Not connected, stop openvpn, set last connected network to no network 177 | lastNetwork = -1; 178 | if (sendusr1) { 179 | network = connectState.PENDINGDISCONNECT; 180 | mDisconnectHandler.postDelayed(mDelayDisconnectRunnable, DISCONNECT_WAIT * 1000); 181 | } 182 | } 183 | if (!netstatestring.equals(lastStateMsg)) VpnStatus.logInfo(R.string.netstatus, netstatestring); 184 | lastStateMsg = netstatestring; 185 | } 186 | 187 | public boolean isUserPaused() { 188 | return userpause == connectState.DISCONNECTED; 189 | } 190 | 191 | private boolean shouldBeConnected() { 192 | return (screen == connectState.SHOULDBECONNECTED && userpause == connectState.SHOULDBECONNECTED && 193 | network == connectState.SHOULDBECONNECTED); 194 | } 195 | 196 | private pauseReason getPauseReason() { 197 | if (userpause == connectState.DISCONNECTED) return pauseReason.userPause; 198 | if (screen == connectState.DISCONNECTED) return pauseReason.screenOff; 199 | if (network == connectState.DISCONNECTED) return pauseReason.noNetwork; 200 | return pauseReason.userPause; 201 | } 202 | 203 | private NetworkInfo getCurrentNetworkInfo(Context context) { 204 | ConnectivityManager conn = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 205 | return conn.getActiveNetworkInfo(); 206 | } 207 | 208 | enum connectState { 209 | SHOULDBECONNECTED, 210 | PENDINGDISCONNECT, 211 | DISCONNECTED 212 | } 213 | 214 | static class Datapoint { 215 | long timestamp; 216 | long data; 217 | private Datapoint(long t, long d) { 218 | timestamp = t; 219 | data = d; 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /vpn/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | package de.blinkt.openvpn.core; 6 | 7 | import android.app.Application; 8 | 9 | import de.blinkt.openvpn.BuildConfig; 10 | 11 | /* 12 | import org.acra.ACRA; 13 | import org.acra.ReportingInteractionMode; 14 | import org.acra.annotation.ReportsCrashes; 15 | */ 16 | /* 17 | @ReportsCrashes( 18 | formKey = "", 19 | formUri = "http://reports.blinkt.de/report-icsopenvpn", 20 | reportType = org.acra.sender.HttpSender.Type.JSON, 21 | httpMethod = org.acra.sender.HttpSender.Method.PUT, 22 | formUriBasicAuthLogin="report-icsopenvpn", 23 | formUriBasicAuthPassword="Tohd4neiF9Ai!!!!111eleven", 24 | mode = ReportingInteractionMode.TOAST, 25 | resToastText = R.string.crash_toast_text 26 | ) 27 | */ 28 | public class ICSOpenVPNApplication extends Application { 29 | @Override 30 | public void onCreate() { 31 | super.onCreate(); 32 | PRNGFixes.apply(); 33 | if (BuildConfig.DEBUG) { 34 | //ACRA.init(this); 35 | } 36 | VpnStatus.initLogCache(getApplicationContext().getCacheDir()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vpn/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2015 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | package de.blinkt.openvpn.core; 6 | 7 | import android.os.Handler; 8 | import android.os.Looper; 9 | import android.os.Message; 10 | 11 | import java.io.BufferedInputStream; 12 | import java.io.File; 13 | import java.io.FileInputStream; 14 | import java.io.FileNotFoundException; 15 | import java.io.FileOutputStream; 16 | import java.io.IOException; 17 | import java.io.InputStream; 18 | import java.io.OutputStream; 19 | import java.io.UnsupportedEncodingException; 20 | import java.nio.BufferOverflowException; 21 | import java.nio.ByteBuffer; 22 | import java.util.Locale; 23 | 24 | import de.blinkt.openvpn.R; 25 | 26 | /** 27 | * Created by arne on 23.01.16. 28 | */ 29 | class LogFileHandler extends Handler { 30 | public static final int LOG_MESSAGE = 103; 31 | public static final int MAGIC_BYTE = 0x55; 32 | public static final String LOGFILE_NAME = "logcache.dat"; 33 | final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); 34 | static final int TRIM_LOG_FILE = 100; 35 | static final int FLUSH_TO_DISK = 101; 36 | static final int LOG_INIT = 102; 37 | protected OutputStream mLogFile; 38 | 39 | public LogFileHandler(Looper looper) { 40 | super(looper); 41 | } 42 | 43 | public static String bytesToHex(byte[] bytes, int len) { 44 | len = Math.min(bytes.length, len); 45 | char[] hexChars = new char[len * 2]; 46 | for (int j = 0; j < len; j++) { 47 | int v = bytes[j] & 0xFF; 48 | hexChars[j * 2] = hexArray[v >>> 4]; 49 | hexChars[j * 2 + 1] = hexArray[v & 0x0F]; 50 | } 51 | return new String(hexChars); 52 | } 53 | 54 | @Override 55 | public void handleMessage(Message msg) { 56 | try { 57 | if (msg.what == LOG_INIT) { 58 | if (mLogFile != null) throw new RuntimeException("mLogFile not null"); 59 | readLogCache((File) msg.obj); 60 | openLogFile((File) msg.obj); 61 | } else if (msg.what == LOG_MESSAGE && msg.obj instanceof LogItem) { 62 | // Ignore log messages if not yet initialized 63 | if (mLogFile == null) return; 64 | writeLogItemToDisk((LogItem) msg.obj); 65 | } else if (msg.what == TRIM_LOG_FILE) { 66 | trimLogFile(); 67 | for (LogItem li : VpnStatus.getlogbuffer()) 68 | writeLogItemToDisk(li); 69 | } else if (msg.what == FLUSH_TO_DISK) { 70 | flushToDisk(); 71 | } 72 | } catch (IOException | BufferOverflowException e) { 73 | e.printStackTrace(); 74 | VpnStatus.logError("Error during log cache: " + msg.what); 75 | VpnStatus.logException(e); 76 | } 77 | } 78 | 79 | private void flushToDisk() throws IOException { 80 | mLogFile.flush(); 81 | } 82 | 83 | private void trimLogFile() { 84 | try { 85 | mLogFile.flush(); 86 | ((FileOutputStream) mLogFile).getChannel().truncate(0); 87 | } catch (IOException e) { 88 | e.printStackTrace(); 89 | } 90 | } 91 | 92 | private void writeLogItemToDisk(LogItem li) throws IOException { 93 | // We do not really care if the log cache breaks between Android upgrades, 94 | // write binary format to disc 95 | byte[] liBytes = li.getMarschaledBytes(); 96 | writeEscapedBytes(liBytes); 97 | } 98 | 99 | public void writeEscapedBytes(byte[] bytes) throws IOException { 100 | int magic = 0; 101 | for (byte b : bytes) 102 | if (b == MAGIC_BYTE || b == MAGIC_BYTE + 1) magic++; 103 | byte eBytes[] = new byte[bytes.length + magic]; 104 | int i = 0; 105 | for (byte b : bytes) { 106 | if (b == MAGIC_BYTE || b == MAGIC_BYTE + 1) { 107 | eBytes[i++] = MAGIC_BYTE + 1; 108 | eBytes[i++] = (byte) (b - MAGIC_BYTE); 109 | } else { 110 | eBytes[i++] = b; 111 | } 112 | } 113 | byte[] lenBytes = ByteBuffer.allocate(4).putInt(bytes.length).array(); 114 | synchronized (mLogFile) { 115 | mLogFile.write(MAGIC_BYTE); 116 | mLogFile.write(lenBytes); 117 | mLogFile.write(eBytes); 118 | } 119 | } 120 | 121 | private void openLogFile(File cacheDir) throws FileNotFoundException { 122 | File logfile = new File(cacheDir, LOGFILE_NAME); 123 | mLogFile = new FileOutputStream(logfile); 124 | } 125 | 126 | private void readLogCache(File cacheDir) { 127 | try { 128 | File logfile = new File(cacheDir, LOGFILE_NAME); 129 | if (!logfile.exists() || !logfile.canRead()) return; 130 | readCacheContents(new FileInputStream(logfile)); 131 | } catch (IOException | RuntimeException e) { 132 | VpnStatus.logError("Reading cached logfile failed"); 133 | VpnStatus.logException(e); 134 | e.printStackTrace(); 135 | // ignore reading file error 136 | } 137 | } 138 | 139 | protected void readCacheContents(InputStream in) throws IOException { 140 | BufferedInputStream logFile = new BufferedInputStream(in); 141 | byte[] buf = new byte[16384]; 142 | int read = logFile.read(buf, 0, 5); 143 | int itemsRead = 0; 144 | readloop: 145 | while (read >= 5) { 146 | int skipped = 0; 147 | while (buf[skipped] != MAGIC_BYTE) { 148 | skipped++; 149 | if (!(logFile.read(buf, skipped + 4, 1) == 1) || skipped + 10 > buf.length) { 150 | VpnStatus.logDebug(String.format(Locale.US, "Skipped %d bytes and no a magic byte found", skipped)); 151 | break readloop; 152 | } 153 | } 154 | if (skipped > 0) VpnStatus.logDebug(String.format(Locale.US, "Skipped %d bytes before finding a magic byte", skipped)); 155 | int len = ByteBuffer.wrap(buf, skipped + 1, 4).asIntBuffer().get(); 156 | // Marshalled LogItem 157 | int pos = 0; 158 | byte buf2[] = new byte[buf.length]; 159 | while (pos < len) { 160 | byte b = (byte) logFile.read(); 161 | if (b == MAGIC_BYTE) { 162 | VpnStatus.logDebug(String.format(Locale.US, "Unexpected magic byte found at pos %d, abort current log item", pos)); 163 | read = logFile.read(buf, 1, 4) + 1; 164 | continue readloop; 165 | } else if (b == MAGIC_BYTE + 1) { 166 | b = (byte) logFile.read(); 167 | if (b == 0) b = MAGIC_BYTE; 168 | else if (b == 1) b = MAGIC_BYTE + 1; 169 | else { 170 | VpnStatus.logDebug(String.format(Locale.US, "Escaped byte not 0 or 1: %d", b)); 171 | read = logFile.read(buf, 1, 4) + 1; 172 | continue readloop; 173 | } 174 | } 175 | buf2[pos++] = b; 176 | } 177 | restoreLogItem(buf2, len); 178 | //Next item 179 | read = logFile.read(buf, 0, 5); 180 | itemsRead++; 181 | if (itemsRead > 2 * VpnStatus.MAXLOGENTRIES) { 182 | VpnStatus.logError("Too many logentries read from cache, aborting."); 183 | read = 0; 184 | } 185 | } 186 | VpnStatus.logDebug(R.string.reread_log, itemsRead); 187 | } 188 | 189 | protected void restoreLogItem(byte[] buf, int len) throws UnsupportedEncodingException { 190 | LogItem li = new LogItem(buf, len); 191 | if (li.verify()) { 192 | VpnStatus.newLogItem(li, true); 193 | } else { 194 | VpnStatus.logError(String.format(Locale.getDefault(), "Could not read log item from file: %d: %s", len, bytesToHex(buf, Math.max(len, 80)))); 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /vpn/src/main/java/de/blinkt/openvpn/core/LogItem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | package de.blinkt.openvpn.core; 6 | 7 | import android.annotation.SuppressLint; 8 | import android.content.Context; 9 | import android.content.pm.PackageInfo; 10 | import android.content.pm.PackageManager; 11 | import android.content.pm.Signature; 12 | import android.os.Parcel; 13 | import android.os.Parcelable; 14 | 15 | import java.io.ByteArrayInputStream; 16 | import java.io.UnsupportedEncodingException; 17 | import java.nio.BufferOverflowException; 18 | import java.nio.ByteBuffer; 19 | import java.security.MessageDigest; 20 | import java.security.NoSuchAlgorithmException; 21 | import java.security.cert.CertificateException; 22 | import java.security.cert.CertificateFactory; 23 | import java.security.cert.X509Certificate; 24 | import java.util.Arrays; 25 | import java.util.FormatFlagsConversionMismatchException; 26 | import java.util.Locale; 27 | import java.util.UnknownFormatConversionException; 28 | 29 | import de.blinkt.openvpn.R; 30 | 31 | /** 32 | * 33 | * Created by arne on 24.04.16. 34 | */ 35 | class LogItem implements Parcelable { 36 | public static final Creator CREATOR = new Creator() { 37 | public LogItem createFromParcel(Parcel in) { 38 | return new LogItem(in); 39 | } 40 | 41 | public LogItem[] newArray(int size) { 42 | return new LogItem[size]; 43 | } 44 | }; 45 | // Default log priority 46 | private VpnStatus.LogLevel mLevel = VpnStatus.LogLevel.INFO; 47 | private Object[] mArgs = null; 48 | private String mMessage = null; 49 | private int mRessourceId; 50 | private long logtime = System.currentTimeMillis(); 51 | private int mVerbosityLevel = -1; 52 | 53 | private LogItem(int ressourceId, Object[] args) { 54 | mRessourceId = ressourceId; 55 | mArgs = args; 56 | } 57 | 58 | LogItem(VpnStatus.LogLevel level, int verblevel, String message) { 59 | mMessage = message; 60 | mLevel = level; 61 | mVerbosityLevel = verblevel; 62 | } 63 | 64 | LogItem(byte[] in, int length) throws UnsupportedEncodingException { 65 | ByteBuffer bb = ByteBuffer.wrap(in, 0, length); 66 | bb.get(); // ignore version 67 | logtime = bb.getLong(); 68 | mVerbosityLevel = bb.getInt(); 69 | mLevel = VpnStatus.LogLevel.getEnumByValue(bb.getInt()); 70 | mRessourceId = bb.getInt(); 71 | int len = bb.getInt(); 72 | if (len == 0) { 73 | mMessage = null; 74 | } else { 75 | if (len > bb.remaining()) throw new IndexOutOfBoundsException("String length " + len + " is bigger than remaining bytes " + bb.remaining()); 76 | byte[] utf8bytes = new byte[len]; 77 | bb.get(utf8bytes); 78 | mMessage = new String(utf8bytes, "UTF-8"); 79 | } 80 | int numArgs = bb.getInt(); 81 | if (numArgs > 30) { 82 | throw new IndexOutOfBoundsException("Too many arguments for Logitem to unmarschal"); 83 | } 84 | if (numArgs == 0) { 85 | mArgs = null; 86 | } else { 87 | mArgs = new Object[numArgs]; 88 | for (int i = 0; i < numArgs; i++) { 89 | char type = bb.getChar(); 90 | switch (type) { 91 | case 's': 92 | mArgs[i] = unmarschalString(bb); 93 | break; 94 | case 'i': 95 | mArgs[i] = bb.getInt(); 96 | break; 97 | case 'd': 98 | mArgs[i] = bb.getDouble(); 99 | break; 100 | case 'f': 101 | mArgs[i] = bb.getFloat(); 102 | break; 103 | case 'l': 104 | mArgs[i] = bb.getLong(); 105 | break; 106 | case '0': 107 | mArgs[i] = null; 108 | break; 109 | default: 110 | throw new UnsupportedEncodingException("Unknown format type: " + type); 111 | } 112 | } 113 | } 114 | if (bb.hasRemaining()) throw new UnsupportedEncodingException(bb.remaining() + " bytes left after unmarshaling everything"); 115 | } 116 | 117 | private LogItem(Parcel in) { 118 | mArgs = in.readArray(Object.class.getClassLoader()); 119 | mMessage = in.readString(); 120 | mRessourceId = in.readInt(); 121 | mLevel = VpnStatus.LogLevel.getEnumByValue(in.readInt()); 122 | mVerbosityLevel = in.readInt(); 123 | logtime = in.readLong(); 124 | } 125 | 126 | LogItem(VpnStatus.LogLevel loglevel, int ressourceId, Object... args) { 127 | mRessourceId = ressourceId; 128 | mArgs = args; 129 | mLevel = loglevel; 130 | } 131 | 132 | LogItem(VpnStatus.LogLevel loglevel, String msg) { 133 | mLevel = loglevel; 134 | mMessage = msg; 135 | } 136 | 137 | LogItem(VpnStatus.LogLevel loglevel, int ressourceId) { 138 | mRessourceId = ressourceId; 139 | mLevel = loglevel; 140 | } 141 | 142 | // TextUtils.join will cause not macked exeception in tests .... 143 | private static String join(CharSequence delimiter, Object[] tokens) { 144 | StringBuilder sb = new StringBuilder(); 145 | boolean firstTime = true; 146 | for (Object token : tokens) { 147 | if (firstTime) { 148 | firstTime = false; 149 | } else { 150 | sb.append(delimiter); 151 | } 152 | sb.append(token); 153 | } 154 | return sb.toString(); 155 | } 156 | 157 | @Override 158 | public int describeContents() { 159 | return 0; 160 | } 161 | 162 | @Override 163 | public void writeToParcel(Parcel dest, int flags) { 164 | dest.writeArray(mArgs); 165 | dest.writeString(mMessage); 166 | dest.writeInt(mRessourceId); 167 | dest.writeInt(mLevel.getInt()); 168 | dest.writeInt(mVerbosityLevel); 169 | dest.writeLong(logtime); 170 | } 171 | 172 | @Override 173 | public boolean equals(Object obj) { 174 | if (!(obj instanceof LogItem)) return obj.equals(this); 175 | LogItem other = (LogItem) obj; 176 | return Arrays.equals(mArgs, other.mArgs) && 177 | ((other.mMessage == null && mMessage == null) || mMessage.equals(other.mMessage)) && 178 | mRessourceId == other.mRessourceId && 179 | ((mLevel == null && other.mLevel == null) || other.mLevel.equals(mLevel)) && 180 | mVerbosityLevel == other.mVerbosityLevel && 181 | logtime == other.logtime; 182 | } 183 | 184 | byte[] getMarschaledBytes() throws UnsupportedEncodingException, BufferOverflowException { 185 | ByteBuffer bb = ByteBuffer.allocate(16384); 186 | bb.put((byte) 0x0); //version 187 | bb.putLong(logtime); //8 188 | bb.putInt(mVerbosityLevel); //4 189 | bb.putInt(mLevel.getInt()); 190 | bb.putInt(mRessourceId); 191 | if (mMessage == null || mMessage.length() == 0) { 192 | bb.putInt(0); 193 | } else { 194 | marschalString(mMessage, bb); 195 | } 196 | if (mArgs == null || mArgs.length == 0) { 197 | bb.putInt(0); 198 | } else { 199 | bb.putInt(mArgs.length); 200 | for (Object o : mArgs) { 201 | if (o instanceof String) { 202 | bb.putChar('s'); 203 | marschalString((String) o, bb); 204 | } else if (o instanceof Integer) { 205 | bb.putChar('i'); 206 | bb.putInt((Integer) o); 207 | } else if (o instanceof Float) { 208 | bb.putChar('f'); 209 | bb.putFloat((Float) o); 210 | } else if (o instanceof Double) { 211 | bb.putChar('d'); 212 | bb.putDouble((Double) o); 213 | } else if (o instanceof Long) { 214 | bb.putChar('l'); 215 | bb.putLong((Long) o); 216 | } else if (o == null) { 217 | bb.putChar('0'); 218 | } else { 219 | VpnStatus.logDebug("Unknown object for LogItem marschaling " + o); 220 | bb.putChar('s'); 221 | marschalString(o.toString(), bb); 222 | } 223 | } 224 | } 225 | int pos = bb.position(); 226 | bb.rewind(); 227 | return Arrays.copyOf(bb.array(), pos); 228 | } 229 | 230 | private void marschalString(String str, ByteBuffer bb) throws UnsupportedEncodingException { 231 | byte[] utf8bytes = str.getBytes("UTF-8"); 232 | bb.putInt(utf8bytes.length); 233 | bb.put(utf8bytes); 234 | } 235 | 236 | private String unmarschalString(ByteBuffer bb) throws UnsupportedEncodingException { 237 | int len = bb.getInt(); 238 | byte[] utf8bytes = new byte[len]; 239 | bb.get(utf8bytes); 240 | return new String(utf8bytes, "UTF-8"); 241 | } 242 | 243 | String getString(Context c) { 244 | try { 245 | if (mMessage != null) { 246 | return mMessage; 247 | } else { 248 | if (c != null) { 249 | if (mRessourceId == R.string.mobile_info) return getMobileInfoString(c); 250 | if (mArgs == null) return c.getString(mRessourceId); 251 | else return c.getString(mRessourceId, mArgs); 252 | } else { 253 | String str = String.format(Locale.ENGLISH, "Log (no context) resid %d", mRessourceId); 254 | if (mArgs != null) str += join("|", mArgs); 255 | return str; 256 | } 257 | } 258 | } catch (UnknownFormatConversionException e) { 259 | if (c != null) throw new UnknownFormatConversionException(e.getLocalizedMessage() + getString(null)); 260 | else throw e; 261 | } catch (FormatFlagsConversionMismatchException e) { 262 | if (c != null) throw new FormatFlagsConversionMismatchException(e.getLocalizedMessage() + getString(null), e.getConversion()); 263 | else throw e; 264 | } 265 | } 266 | 267 | public VpnStatus.LogLevel getLogLevel() { 268 | return mLevel; 269 | } 270 | 271 | @Override 272 | public String toString() { 273 | return getString(null); 274 | } 275 | 276 | // The lint is wrong here 277 | @SuppressLint("StringFormatMatches") 278 | private String getMobileInfoString(Context c) { 279 | c.getPackageManager(); 280 | String apksign = "error getting package signature"; 281 | String version = "error getting version"; 282 | try { 283 | @SuppressLint("PackageManagerGetSignatures") Signature raw = c.getPackageManager().getPackageInfo(c.getPackageName(), PackageManager.GET_SIGNATURES).signatures[0]; 284 | CertificateFactory cf = CertificateFactory.getInstance("X.509"); 285 | X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(raw.toByteArray())); 286 | MessageDigest md = MessageDigest.getInstance("SHA-1"); 287 | byte[] der = cert.getEncoded(); 288 | md.update(der); 289 | byte[] digest = md.digest(); 290 | if (Arrays.equals(digest, VpnStatus.officalkey)) apksign = c.getString(R.string.official_build); 291 | else if (Arrays.equals(digest, VpnStatus.officaldebugkey)) apksign = c.getString(R.string.debug_build); 292 | else if (Arrays.equals(digest, VpnStatus.amazonkey)) apksign = "amazon version"; 293 | else if (Arrays.equals(digest, VpnStatus.fdroidkey)) apksign = "F-Droid built and signed version"; 294 | else apksign = c.getString(R.string.built_by, cert.getSubjectX500Principal().getName()); 295 | PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0); 296 | version = packageinfo.versionName; 297 | } catch (PackageManager.NameNotFoundException | CertificateException | 298 | NoSuchAlgorithmException ignored) { 299 | } 300 | Object[] argsext = Arrays.copyOf(mArgs, mArgs.length); 301 | argsext[argsext.length - 1] = apksign; 302 | argsext[argsext.length - 2] = version; 303 | return c.getString(R.string.mobile_info, argsext); 304 | } 305 | 306 | long getLogtime() { 307 | return logtime; 308 | } 309 | 310 | public int getVerbosityLevel() { 311 | if (mVerbosityLevel == -1) { 312 | // Hack: 313 | // For message not from OpenVPN, report the status level as log level 314 | return mLevel.getInt(); 315 | } 316 | return mVerbosityLevel; 317 | } 318 | 319 | boolean verify() { 320 | return mLevel != null && !(mMessage == null && mRessourceId == 0); 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /vpn/src/main/java/de/blinkt/openvpn/core/LollipopDeviceStateListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | package de.blinkt.openvpn.core; 6 | 7 | import android.annotation.TargetApi; 8 | import android.net.ConnectivityManager; 9 | import android.net.LinkProperties; 10 | import android.net.Network; 11 | import android.net.NetworkCapabilities; 12 | import android.os.Build; 13 | 14 | /** 15 | * Created by arne on 26.11.14. 16 | */ 17 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 18 | public class LollipopDeviceStateListener extends ConnectivityManager.NetworkCallback { 19 | private String mLastConnectedStatus; 20 | private String mLastLinkProperties; 21 | private String mLastNetworkCapabilities; 22 | 23 | @Override 24 | public void onAvailable(Network network) { 25 | super.onAvailable(network); 26 | if (!network.toString().equals(mLastConnectedStatus)) { 27 | mLastConnectedStatus = network.toString(); 28 | VpnStatus.logDebug("Connected to " + mLastConnectedStatus); 29 | } 30 | } 31 | 32 | @Override 33 | public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) { 34 | super.onLinkPropertiesChanged(network, linkProperties); 35 | if (!linkProperties.toString().equals(mLastLinkProperties)) { 36 | mLastLinkProperties = linkProperties.toString(); 37 | VpnStatus.logDebug(String.format("Linkproperties of %s: %s", network, linkProperties)); 38 | } 39 | } 40 | 41 | @Override 42 | public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { 43 | super.onCapabilitiesChanged(network, networkCapabilities); 44 | if (!networkCapabilities.toString().equals(mLastNetworkCapabilities)) { 45 | mLastNetworkCapabilities = networkCapabilities.toString(); 46 | VpnStatus.logDebug(String.format("Network capabilities of %s: %s", network, networkCapabilities)); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /vpn/src/main/java/de/blinkt/openvpn/core/NativeUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | package de.blinkt.openvpn.core; 6 | 7 | import android.os.Build; 8 | 9 | import java.security.InvalidKeyException; 10 | 11 | public class NativeUtils { 12 | static { 13 | System.loadLibrary("opvpnutil"); 14 | if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) System.loadLibrary("jbcrypto"); 15 | } 16 | 17 | public static native byte[] rsasign(byte[] input, int pkey) throws InvalidKeyException; 18 | 19 | public static native String[] getIfconfig() throws IllegalArgumentException; 20 | 21 | static native void jniclose(int fdint); 22 | 23 | public static native String getNativeAPI(); 24 | } 25 | -------------------------------------------------------------------------------- /vpn/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | package de.blinkt.openvpn.core; 6 | 7 | import android.os.Build; 8 | import android.support.annotation.NonNull; 9 | 10 | import junit.framework.Assert; 11 | 12 | import java.math.BigInteger; 13 | import java.net.Inet6Address; 14 | import java.util.Collection; 15 | import java.util.Locale; 16 | import java.util.PriorityQueue; 17 | import java.util.TreeSet; 18 | import java.util.Vector; 19 | 20 | import de.blinkt.openvpn.BuildConfig; 21 | 22 | public class NetworkSpace { 23 | TreeSet mIpAddresses = new TreeSet(); 24 | 25 | public Collection getNetworks(boolean included) { 26 | Vector ips = new Vector(); 27 | for (ipAddress ip : mIpAddresses) { 28 | if (ip.included == included) ips.add(ip); 29 | } 30 | return ips; 31 | } 32 | 33 | public void clear() { 34 | mIpAddresses.clear(); 35 | } 36 | 37 | void addIP(CIDRIP cidrIp, boolean include) { 38 | mIpAddresses.add(new ipAddress(cidrIp, include)); 39 | } 40 | 41 | public void addIPSplit(CIDRIP cidrIp, boolean include) { 42 | ipAddress newIP = new ipAddress(cidrIp, include); 43 | ipAddress[] splitIps = newIP.split(); 44 | for (ipAddress split : splitIps) 45 | mIpAddresses.add(split); 46 | } 47 | 48 | void addIPv6(Inet6Address address, int mask, boolean included) { 49 | mIpAddresses.add(new ipAddress(address, mask, included)); 50 | } 51 | 52 | TreeSet generateIPList() { 53 | PriorityQueue networks = new PriorityQueue(mIpAddresses); 54 | TreeSet ipsDone = new TreeSet(); 55 | ipAddress currentNet = networks.poll(); 56 | if (currentNet == null) return ipsDone; 57 | while (currentNet != null) { 58 | // Check if it and the next of it are compatible 59 | ipAddress nextNet = networks.poll(); 60 | if (BuildConfig.DEBUG) Assert.assertNotNull(currentNet); 61 | if (nextNet == null || currentNet.getLastAddress().compareTo(nextNet.getFirstAddress()) == -1) { 62 | // Everything good, no overlapping nothing to do 63 | ipsDone.add(currentNet); 64 | currentNet = nextNet; 65 | } else { 66 | // This network is smaller or equal to the next but has the same base address 67 | if (currentNet.getFirstAddress().equals(nextNet.getFirstAddress()) && currentNet.networkMask >= nextNet.networkMask) { 68 | if (currentNet.included == nextNet.included) { 69 | // Included in the next next and same type 70 | // Simply forget our current network 71 | currentNet = nextNet; 72 | } else { 73 | // our currentNet is included in next and types differ. Need to split the next network 74 | ipAddress[] newNets = nextNet.split(); 75 | // TODO: The contains method of the Priority is stupid linear search 76 | // First add the second half to keep the order in networks 77 | if (!networks.contains(newNets[1])) networks.add(newNets[1]); 78 | if (newNets[0].getLastAddress().equals(currentNet.getLastAddress())) { 79 | if (BuildConfig.DEBUG) Assert.assertEquals(newNets[0].networkMask, currentNet.networkMask); 80 | // Don't add the lower half that would conflict with currentNet 81 | } else { 82 | if (!networks.contains(newNets[0])) networks.add(newNets[0]); 83 | } 84 | // Keep currentNet as is 85 | } 86 | } else { 87 | if (BuildConfig.DEBUG) { 88 | Assert.assertTrue(currentNet.networkMask < nextNet.networkMask); 89 | Assert.assertTrue(nextNet.getFirstAddress().compareTo(currentNet.getFirstAddress()) == 1); 90 | Assert.assertTrue(currentNet.getLastAddress().compareTo(nextNet.getLastAddress()) != -1); 91 | } 92 | // This network is bigger than the next and last ip of current >= next 93 | //noinspection StatementWithEmptyBody 94 | if (currentNet.included == nextNet.included) { 95 | // Next network is in included in our network with the same type, 96 | // simply ignore the next and move on 97 | } else { 98 | // We need to split our network 99 | ipAddress[] newNets = currentNet.split(); 100 | if (newNets[1].networkMask == nextNet.networkMask) { 101 | if (BuildConfig.DEBUG) { 102 | Assert.assertTrue(newNets[1].getFirstAddress().equals(nextNet.getFirstAddress())); 103 | Assert.assertTrue(newNets[1].getLastAddress().equals(currentNet.getLastAddress())); 104 | // split second equal the next network, do not add it 105 | } 106 | networks.add(nextNet); 107 | } else { 108 | // Add the smaller network first 109 | networks.add(newNets[1]); 110 | networks.add(nextNet); 111 | } 112 | currentNet = newNets[0]; 113 | } 114 | } 115 | } 116 | } 117 | return ipsDone; 118 | } 119 | 120 | Collection getPositiveIPList() { 121 | TreeSet ipsSorted = generateIPList(); 122 | Vector ips = new Vector(); 123 | for (ipAddress ia : ipsSorted) { 124 | if (ia.included) ips.add(ia); 125 | } 126 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 127 | // Include postive routes from the original set under < 4.4 since these might overrule the local 128 | // network but only if no smaller negative route exists 129 | for (ipAddress origIp : mIpAddresses) { 130 | if (!origIp.included) continue; 131 | // The netspace exists 132 | if (ipsSorted.contains(origIp)) continue; 133 | boolean skipIp = false; 134 | // If there is any smaller net that is excluded we may not add the positive route back 135 | for (ipAddress calculatedIp : ipsSorted) { 136 | if (!calculatedIp.included && origIp.containsNet(calculatedIp)) { 137 | skipIp = true; 138 | break; 139 | } 140 | } 141 | if (skipIp) continue; 142 | // It is safe to include the IP 143 | ips.add(origIp); 144 | } 145 | } 146 | return ips; 147 | } 148 | 149 | static class ipAddress implements Comparable { 150 | public int networkMask; 151 | private BigInteger netAddress; 152 | private boolean included; 153 | private boolean isV4; 154 | private BigInteger firstAddress; 155 | private BigInteger lastAddress; 156 | 157 | public ipAddress(CIDRIP ip, boolean include) { 158 | included = include; 159 | netAddress = BigInteger.valueOf(ip.getInt()); 160 | networkMask = ip.len; 161 | isV4 = true; 162 | } 163 | 164 | public ipAddress(Inet6Address address, int mask, boolean include) { 165 | networkMask = mask; 166 | included = include; 167 | int s = 128; 168 | netAddress = BigInteger.ZERO; 169 | for (byte b : address.getAddress()) { 170 | s -= 8; 171 | netAddress = netAddress.add(BigInteger.valueOf((b & 0xFF)).shiftLeft(s)); 172 | } 173 | } 174 | 175 | ipAddress(BigInteger baseAddress, int mask, boolean included, boolean isV4) { 176 | this.netAddress = baseAddress; 177 | this.networkMask = mask; 178 | this.included = included; 179 | this.isV4 = isV4; 180 | } 181 | 182 | /** 183 | * sorts the networks with following criteria: 184 | * 1. compares first 1 of the network 185 | * 2. smaller networks are returned as smaller 186 | */ 187 | @Override 188 | public int compareTo(@NonNull ipAddress another) { 189 | int comp = getFirstAddress().compareTo(another.getFirstAddress()); 190 | if (comp != 0) return comp; 191 | if (networkMask > another.networkMask) return -1; 192 | else if (another.networkMask == networkMask) return 0; 193 | else return 1; 194 | } 195 | 196 | /** 197 | * Warning ignores the included integer 198 | * 199 | * @param o the object to compare this instance with. 200 | */ 201 | @Override 202 | public boolean equals(Object o) { 203 | if (!(o instanceof ipAddress)) return super.equals(o); 204 | ipAddress on = (ipAddress) o; 205 | return (networkMask == on.networkMask) && on.getFirstAddress().equals(getFirstAddress()); 206 | } 207 | 208 | public BigInteger getLastAddress() { 209 | if (lastAddress == null) lastAddress = getMaskedAddress(true); 210 | return lastAddress; 211 | } 212 | 213 | public BigInteger getFirstAddress() { 214 | if (firstAddress == null) firstAddress = getMaskedAddress(false); 215 | return firstAddress; 216 | } 217 | 218 | private BigInteger getMaskedAddress(boolean one) { 219 | BigInteger numAddress = netAddress; 220 | int numBits; 221 | if (isV4) { 222 | numBits = 32 - networkMask; 223 | } else { 224 | numBits = 128 - networkMask; 225 | } 226 | for (int i = 0; i < numBits; i++) { 227 | if (one) numAddress = numAddress.setBit(i); 228 | else numAddress = numAddress.clearBit(i); 229 | } 230 | return numAddress; 231 | } 232 | 233 | @Override 234 | public String toString() { 235 | //String in = included ? "+" : "-"; 236 | if (isV4) return String.format(Locale.US, "%s/%d", getIPv4Address(), networkMask); 237 | else return String.format(Locale.US, "%s/%d", getIPv6Address(), networkMask); 238 | } 239 | 240 | public ipAddress[] split() { 241 | ipAddress firstHalf = new ipAddress(getFirstAddress(), networkMask + 1, included, isV4); 242 | ipAddress secondHalf = new ipAddress(firstHalf.getLastAddress().add(BigInteger.ONE), networkMask + 1, included, isV4); 243 | if (BuildConfig.DEBUG) Assert.assertTrue(secondHalf.getLastAddress().equals(getLastAddress())); 244 | return new ipAddress[]{firstHalf, secondHalf}; 245 | } 246 | 247 | String getIPv4Address() { 248 | if (BuildConfig.DEBUG) { 249 | Assert.assertTrue(isV4); 250 | Assert.assertTrue(netAddress.longValue() <= 0xffffffffl); 251 | Assert.assertTrue(netAddress.longValue() >= 0); 252 | } 253 | long ip = netAddress.longValue(); 254 | return String.format(Locale.US, "%d.%d.%d.%d", (ip >> 24) % 256, (ip >> 16) % 256, (ip >> 8) % 256, ip % 256); 255 | } 256 | 257 | String getIPv6Address() { 258 | if (BuildConfig.DEBUG) Assert.assertTrue(!isV4); 259 | BigInteger r = netAddress; 260 | String ipv6str = null; 261 | boolean lastPart = true; 262 | while (r.compareTo(BigInteger.ZERO) == 1) { 263 | long part = r.mod(BigInteger.valueOf(0x10000)).longValue(); 264 | if (ipv6str != null || part != 0) { 265 | if (ipv6str == null && !lastPart) ipv6str = ":"; 266 | if (lastPart) ipv6str = String.format(Locale.US, "%x", part, ipv6str); 267 | else ipv6str = String.format(Locale.US, "%x:%s", part, ipv6str); 268 | } 269 | r = r.shiftRight(16); 270 | lastPart = false; 271 | } 272 | if (ipv6str == null) return "::"; 273 | return ipv6str; 274 | } 275 | 276 | public boolean containsNet(ipAddress network) { 277 | // this.first >= net.first && this.last <= net.last 278 | BigInteger ourFirst = getFirstAddress(); 279 | BigInteger ourLast = getLastAddress(); 280 | BigInteger netFirst = network.getFirstAddress(); 281 | BigInteger netLast = network.getLastAddress(); 282 | boolean a = ourFirst.compareTo(netFirst) != 1; 283 | boolean b = ourLast.compareTo(netLast) != -1; 284 | return a && b; 285 | } 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /vpn/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | package de.blinkt.openvpn.core; 6 | 7 | public interface OpenVPNManagement { 8 | int mBytecountInterval = 2; 9 | 10 | void reconnect(); 11 | 12 | void pause(pauseReason reason); 13 | 14 | void resume(); 15 | 16 | /** 17 | * @param replaceConnection True if the VPN is connected by a new connection. 18 | * @return true if there was a process that has been send a stop signal 19 | */ 20 | boolean stopVPN(boolean replaceConnection); 21 | 22 | /* 23 | * Rebind the interface 24 | */ 25 | void networkChange(boolean sameNetwork); 26 | 27 | void setPauseCallback(PausedStateCallback callback); 28 | 29 | enum pauseReason { 30 | noNetwork, 31 | userPause, 32 | screenOff, 33 | } 34 | 35 | interface PausedStateCallback { 36 | boolean shouldBeRunning(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vpn/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | package de.blinkt.openvpn.core; 6 | 7 | import android.annotation.SuppressLint; 8 | import android.util.Log; 9 | 10 | import java.io.BufferedReader; 11 | import java.io.BufferedWriter; 12 | import java.io.FileWriter; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.io.InputStreamReader; 16 | import java.text.SimpleDateFormat; 17 | import java.util.Collections; 18 | import java.util.Date; 19 | import java.util.LinkedList; 20 | import java.util.Locale; 21 | import java.util.regex.Matcher; 22 | import java.util.regex.Pattern; 23 | 24 | import de.blinkt.openvpn.R; 25 | import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; 26 | 27 | public class OpenVPNThread implements Runnable { 28 | public static final int M_FATAL = (1 << 4); 29 | public static final int M_NONFATAL = (1 << 5); 30 | public static final int M_WARN = (1 << 6); 31 | public static final int M_DEBUG = (1 << 7); 32 | private static final String DUMP_PATH_STRING = "Dump path: "; 33 | @SuppressLint("SdCardPath") 34 | private static final String BROKEN_PIE_SUPPORT = "/data/data/de.blinkt.openvpn/cache/pievpn"; 35 | private final static String BROKEN_PIE_SUPPORT2 = "syntax error"; 36 | private static final String TAG = "OpenVPN"; 37 | private String[] mArgv; 38 | private Process mProcess; 39 | private String mNativeDir; 40 | private OpenVPNService mService; 41 | private String mDumpPath; 42 | private boolean mBrokenPie = false; 43 | private boolean mNoProcessExitStatus = false; 44 | 45 | public OpenVPNThread(OpenVPNService service, String[] argv, String nativelibdir) { 46 | mArgv = argv; 47 | mNativeDir = nativelibdir; 48 | mService = service; 49 | } 50 | 51 | public void stopProcess() { 52 | mProcess.destroy(); 53 | } 54 | 55 | void setReplaceConnection() { 56 | mNoProcessExitStatus = true; 57 | } 58 | 59 | @Override 60 | public void run() { 61 | try { 62 | Log.i(TAG, "Starting openvpn"); 63 | startOpenVPNThreadArgs(mArgv); 64 | Log.i(TAG, "OpenVPN process exited"); 65 | } catch (Exception e) { 66 | VpnStatus.logException("Starting OpenVPN Thread", e); 67 | Log.e(TAG, "OpenVPNThread Got " + e.toString()); 68 | } finally { 69 | int exitvalue = 0; 70 | try { 71 | if (mProcess != null) exitvalue = mProcess.waitFor(); 72 | } catch (IllegalThreadStateException ite) { 73 | VpnStatus.logError("Illegal Thread state: " + ite.getLocalizedMessage()); 74 | } catch (InterruptedException ie) { 75 | VpnStatus.logError("InterruptedException: " + ie.getLocalizedMessage()); 76 | } 77 | if (exitvalue != 0) { 78 | VpnStatus.logError("Process exited with exit value " + exitvalue); 79 | if (mBrokenPie) { 80 | /* This will probably fail since the NoPIE binary is probably not written */ 81 | String[] noPieArgv = VPNLaunchHelper.replacePieWithNoPie(mArgv); 82 | // We are already noPIE, nothing to gain 83 | if (!noPieArgv.equals(mArgv)) { 84 | mArgv = noPieArgv; 85 | VpnStatus.logInfo("PIE Version could not be executed. Trying no PIE version"); 86 | run(); 87 | return; 88 | } 89 | } 90 | } 91 | if (!mNoProcessExitStatus) VpnStatus.updateStateString("NOPROCESS", "No process running.", R.string.state_noprocess, ConnectionStatus.LEVEL_NOTCONNECTED); 92 | if (mDumpPath != null) { 93 | try { 94 | BufferedWriter logout = new BufferedWriter(new FileWriter(mDumpPath + ".log")); 95 | SimpleDateFormat timeformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.GERMAN); 96 | for (LogItem li : VpnStatus.getlogbuffer()) { 97 | String time = timeformat.format(new Date(li.getLogtime())); 98 | logout.write(time + " " + li.getString(mService) + "\n"); 99 | } 100 | logout.close(); 101 | VpnStatus.logError(R.string.minidump_generated); 102 | } catch (IOException e) { 103 | VpnStatus.logError("Writing minidump log: " + e.getLocalizedMessage()); 104 | } 105 | } 106 | mService.processDied(); 107 | Log.i(TAG, "Exiting"); 108 | } 109 | } 110 | 111 | private void startOpenVPNThreadArgs(String[] argv) { 112 | LinkedList argvlist = new LinkedList(); 113 | Collections.addAll(argvlist, argv); 114 | ProcessBuilder pb = new ProcessBuilder(argvlist); 115 | // Hack O rama 116 | String lbpath = genLibraryPath(argv, pb); 117 | pb.environment().put("LD_LIBRARY_PATH", lbpath); 118 | pb.redirectErrorStream(true); 119 | try { 120 | mProcess = pb.start(); 121 | // Close the output, since we don't need it 122 | mProcess.getOutputStream().close(); 123 | InputStream in = mProcess.getInputStream(); 124 | BufferedReader br = new BufferedReader(new InputStreamReader(in)); 125 | while (true) { 126 | String logline = br.readLine(); 127 | if (logline == null) return; 128 | if (logline.startsWith(DUMP_PATH_STRING)) mDumpPath = logline.substring(DUMP_PATH_STRING.length()); 129 | if (logline.startsWith(BROKEN_PIE_SUPPORT) || logline.contains(BROKEN_PIE_SUPPORT2)) mBrokenPie = true; 130 | // 1380308330.240114 18000002 Send to HTTP proxy: 'X-Online-Host: bla.blabla.com' 131 | Pattern p = Pattern.compile("(\\d+).(\\d+) ([0-9a-f])+ (.*)"); 132 | Matcher m = p.matcher(logline); 133 | if (m.matches()) { 134 | int flags = Integer.parseInt(m.group(3), 16); 135 | String msg = m.group(4); 136 | int logLevel = flags & 0x0F; 137 | VpnStatus.LogLevel logStatus = VpnStatus.LogLevel.INFO; 138 | if ((flags & M_FATAL) != 0) logStatus = VpnStatus.LogLevel.ERROR; 139 | else if ((flags & M_NONFATAL) != 0) logStatus = VpnStatus.LogLevel.WARNING; 140 | else if ((flags & M_WARN) != 0) logStatus = VpnStatus.LogLevel.WARNING; 141 | else if ((flags & M_DEBUG) != 0) logStatus = VpnStatus.LogLevel.VERBOSE; 142 | if (msg.startsWith("MANAGEMENT: CMD")) logLevel = Math.max(4, logLevel); 143 | VpnStatus.logMessageOpenVPN(logStatus, logLevel, msg); 144 | } else { 145 | VpnStatus.logInfo("P:" + logline); 146 | } 147 | if (Thread.interrupted()) { 148 | throw new InterruptedException("OpenVpn process was killed form java code"); 149 | } 150 | } 151 | } catch (InterruptedException | IOException e) { 152 | VpnStatus.logException("Error reading from output of OpenVPN process", e); 153 | stopProcess(); 154 | } 155 | } 156 | 157 | private String genLibraryPath(String[] argv, ProcessBuilder pb) { 158 | // Hack until I find a good way to get the real library path 159 | String applibpath = argv[0].replaceFirst("/cache/.*$", "/lib"); 160 | String lbpath = pb.environment().get("LD_LIBRARY_PATH"); 161 | if (lbpath == null) lbpath = applibpath; 162 | else lbpath = applibpath + ":" + lbpath; 163 | if (!applibpath.equals(mNativeDir)) { 164 | lbpath = mNativeDir + ":" + lbpath; 165 | } 166 | return lbpath; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /vpn/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | package de.blinkt.openvpn.core; 6 | 7 | import android.content.Context; 8 | import android.net.LocalServerSocket; 9 | import android.net.LocalSocket; 10 | import android.net.LocalSocketAddress; 11 | import android.os.Handler; 12 | import android.os.ParcelFileDescriptor; 13 | import android.support.annotation.NonNull; 14 | import android.util.Log; 15 | 16 | import junit.framework.Assert; 17 | 18 | import java.io.FileDescriptor; 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | import java.lang.reflect.InvocationTargetException; 22 | import java.lang.reflect.Method; 23 | import java.net.InetSocketAddress; 24 | import java.net.SocketAddress; 25 | import java.util.Arrays; 26 | import java.util.Collections; 27 | import java.util.LinkedList; 28 | import java.util.Locale; 29 | import java.util.Vector; 30 | 31 | import de.blinkt.openvpn.BuildConfig; 32 | import de.blinkt.openvpn.R; 33 | import de.blinkt.openvpn.VpnProfile; 34 | import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; 35 | 36 | public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { 37 | private static final String TAG = "openvpn"; 38 | private static final Vector active = new Vector<>(); 39 | private final Handler mResumeHandler; 40 | private LocalSocket mSocket; 41 | private VpnProfile mProfile; 42 | private OpenVPNService mOpenVPNService; 43 | private LinkedList mFDList = new LinkedList<>(); 44 | private LocalServerSocket mServerSocket; 45 | private boolean mWaitingForRelease = false; 46 | private long mLastHoldRelease = 0; 47 | private LocalSocket mServerSocketLocal; 48 | private pauseReason lastPauseReason = pauseReason.noNetwork; 49 | private PausedStateCallback mPauseCallback; 50 | private boolean mShuttingDown; 51 | private Runnable mResumeHoldRunnable = new Runnable() { 52 | @Override 53 | public void run() { 54 | if (shouldBeRunning()) { 55 | releaseHoldCmd(); 56 | } 57 | } 58 | }; 59 | 60 | public OpenVpnManagementThread(VpnProfile profile, OpenVPNService openVpnService) { 61 | mProfile = profile; 62 | mOpenVPNService = openVpnService; 63 | mResumeHandler = new Handler(openVpnService.getMainLooper()); 64 | } 65 | 66 | private static boolean stopOpenVPN() { 67 | synchronized (active) { 68 | boolean sendCMD = false; 69 | for (OpenVpnManagementThread mt : active) { 70 | sendCMD = mt.managmentCommand("signal SIGINT\n"); 71 | try { 72 | if (mt.mSocket != null) mt.mSocket.close(); 73 | } catch (IOException e) { 74 | // Ignore close error on already closed socket 75 | } 76 | } 77 | return sendCMD; 78 | } 79 | } 80 | 81 | public boolean openManagementInterface(@NonNull Context c) { 82 | // Could take a while to open connection 83 | int tries = 8; 84 | String socketName = (c.getCacheDir().getAbsolutePath() + "/" + "mgmtsocket"); 85 | // The mServerSocketLocal is transferred to the LocalServerSocket, ignore warning 86 | mServerSocketLocal = new LocalSocket(); 87 | while (tries > 0 && !mServerSocketLocal.isBound()) { 88 | try { 89 | mServerSocketLocal.bind(new LocalSocketAddress(socketName, LocalSocketAddress.Namespace.FILESYSTEM)); 90 | } catch (IOException e) { 91 | // wait 300 ms before retrying 92 | try { 93 | Thread.sleep(300); 94 | } catch (InterruptedException ignored) { 95 | } 96 | } 97 | tries--; 98 | } 99 | try { 100 | mServerSocket = new LocalServerSocket(mServerSocketLocal.getFileDescriptor()); 101 | return true; 102 | } catch (IOException e) { 103 | VpnStatus.logException(e); 104 | } 105 | return false; 106 | } 107 | 108 | /** 109 | * @param cmd command to write to management socket 110 | * @return true if command have been sent 111 | */ 112 | public boolean managmentCommand(String cmd) { 113 | try { 114 | if (mSocket != null && mSocket.getOutputStream() != null) { 115 | mSocket.getOutputStream().write(cmd.getBytes()); 116 | mSocket.getOutputStream().flush(); 117 | return true; 118 | } 119 | } catch (IOException e) { 120 | // Ignore socket stack traces 121 | } 122 | return false; 123 | } 124 | 125 | @Override 126 | public void run() { 127 | byte[] buffer = new byte[2048]; 128 | // mSocket.setSoTimeout(5); // Setting a timeout cannot be that bad 129 | String pendingInput = ""; 130 | synchronized (active) { 131 | active.add(this); 132 | } 133 | try { 134 | // Wait for a client to connect 135 | mSocket = mServerSocket.accept(); 136 | InputStream instream = mSocket.getInputStream(); 137 | // Close the management socket after client connected 138 | try { 139 | mServerSocket.close(); 140 | } catch (IOException e) { 141 | VpnStatus.logException(e); 142 | } 143 | // Closing one of the two sockets also closes the other 144 | //mServerSocketLocal.close(); 145 | while (true) { 146 | int numbytesread = instream.read(buffer); 147 | if (numbytesread == -1) return; 148 | FileDescriptor[] fds = null; 149 | try { 150 | fds = mSocket.getAncillaryFileDescriptors(); 151 | } catch (IOException e) { 152 | VpnStatus.logException("Error reading fds from socket", e); 153 | } 154 | if (fds != null) { 155 | Collections.addAll(mFDList, fds); 156 | } 157 | String input = new String(buffer, 0, numbytesread, "UTF-8"); 158 | pendingInput += input; 159 | pendingInput = processInput(pendingInput); 160 | } 161 | } catch (IOException e) { 162 | if (!e.getMessage().equals("socket closed") && !e.getMessage().equals("Connection reset by peer")) VpnStatus.logException(e); 163 | } 164 | synchronized (active) { 165 | active.remove(this); 166 | } 167 | } 168 | 169 | //! Hack O Rama 2000! 170 | private void protectFileDescriptor(FileDescriptor fd) { 171 | try { 172 | Method getInt = FileDescriptor.class.getDeclaredMethod("getInt$"); 173 | int fdint = (Integer) getInt.invoke(fd); 174 | // You can even get more evil by parsing toString() and extract the int from that :) 175 | boolean result = mOpenVPNService.protect(fdint); 176 | if (!result) VpnStatus.logWarning("Could not protect VPN socket"); 177 | //ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fdint); 178 | //pfd.close(); 179 | NativeUtils.jniclose(fdint); 180 | return; 181 | } catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException | IllegalAccessException | NullPointerException e) { 182 | VpnStatus.logException("Failed to retrieve fd from socket (" + fd + ")", e); 183 | } 184 | Log.d("Openvpn", "Failed to retrieve fd from socket: " + fd); 185 | } 186 | 187 | private String processInput(String pendingInput) { 188 | while (pendingInput.contains("\n")) { 189 | String[] tokens = pendingInput.split("\\r?\\n", 2); 190 | processCommand(tokens[0]); 191 | if (tokens.length == 1) 192 | // No second part, newline was at the end 193 | pendingInput = ""; 194 | else pendingInput = tokens[1]; 195 | } 196 | return pendingInput; 197 | } 198 | 199 | private void processCommand(String command) { 200 | //Log.i(TAG, "Line from managment" + command); 201 | if (command.startsWith(">") && command.contains(":")) { 202 | String[] parts = command.split(":", 2); 203 | String cmd = parts[0].substring(1); 204 | String argument = parts[1]; 205 | switch (cmd) { 206 | case "INFO": 207 | /* Ignore greeting from management */ 208 | return; 209 | case "PASSWORD": 210 | processPWCommand(argument); 211 | break; 212 | case "HOLD": 213 | handleHold(argument); 214 | break; 215 | case "NEED-OK": 216 | processNeedCommand(argument); 217 | break; 218 | case "BYTECOUNT": 219 | processByteCount(argument); 220 | break; 221 | case "STATE": 222 | if (!mShuttingDown) processState(argument); 223 | break; 224 | case "PROXY": 225 | processProxyCMD(argument); 226 | break; 227 | case "LOG": 228 | processLogMessage(argument); 229 | break; 230 | case "RSA_SIGN": 231 | processSignCommand(argument); 232 | break; 233 | default: 234 | VpnStatus.logWarning("MGMT: Got unrecognized command" + command); 235 | Log.i(TAG, "Got unrecognized command" + command); 236 | break; 237 | } 238 | } else if (command.startsWith("SUCCESS:")) { 239 | /* Ignore this kind of message too */ 240 | return; 241 | } else if (command.startsWith("PROTECTFD: ")) { 242 | FileDescriptor fdtoprotect = mFDList.pollFirst(); 243 | if (fdtoprotect != null) protectFileDescriptor(fdtoprotect); 244 | } else { 245 | Log.i(TAG, "Got unrecognized line from managment" + command); 246 | VpnStatus.logWarning("MGMT: Got unrecognized line from management:" + command); 247 | } 248 | } 249 | 250 | private void processLogMessage(String argument) { 251 | String[] args = argument.split(",", 4); 252 | // 0 unix time stamp 253 | // 1 log level N,I,E etc. 254 | /* 255 | (b) zero or more message flags in a single string: 256 | I -- informational 257 | F -- fatal error 258 | N -- non-fatal error 259 | W -- warning 260 | D -- debug, and 261 | */ 262 | // 2 log message 263 | Log.d("OpenVPN", argument); 264 | VpnStatus.LogLevel level; 265 | switch (args[1]) { 266 | case "I": 267 | level = VpnStatus.LogLevel.INFO; 268 | break; 269 | case "W": 270 | level = VpnStatus.LogLevel.WARNING; 271 | break; 272 | case "D": 273 | level = VpnStatus.LogLevel.VERBOSE; 274 | break; 275 | case "F": 276 | level = VpnStatus.LogLevel.ERROR; 277 | break; 278 | default: 279 | level = VpnStatus.LogLevel.INFO; 280 | break; 281 | } 282 | int ovpnlevel = Integer.parseInt(args[2]) & 0x0F; 283 | String msg = args[3]; 284 | if (msg.startsWith("MANAGEMENT: CMD")) ovpnlevel = Math.max(4, ovpnlevel); 285 | VpnStatus.logMessageOpenVPN(level, ovpnlevel, msg); 286 | } 287 | 288 | boolean shouldBeRunning() { 289 | if (mPauseCallback == null) return false; 290 | else return mPauseCallback.shouldBeRunning(); 291 | } 292 | 293 | private void handleHold(String argument) { 294 | int waittime = Integer.parseInt(argument.split(":")[1]); 295 | if (shouldBeRunning()) { 296 | if (waittime > 1) VpnStatus.updateStateString("CONNECTRETRY", String.valueOf(waittime), R.string.state_waitconnectretry, ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET); 297 | mResumeHandler.postDelayed(mResumeHoldRunnable, waittime * 1000); 298 | if (waittime > 5) { 299 | VpnStatus.logInfo(R.string.state_waitconnectretry, String.valueOf(waittime)); 300 | } 301 | } else { 302 | mWaitingForRelease = true; 303 | VpnStatus.updateStatePause(lastPauseReason); 304 | } 305 | } 306 | 307 | private void releaseHoldCmd() { 308 | mResumeHandler.removeCallbacks(mResumeHoldRunnable); 309 | if ((System.currentTimeMillis() - mLastHoldRelease) < 5000) { 310 | try { 311 | Thread.sleep(3000); 312 | } catch (InterruptedException ignored) { 313 | } 314 | } 315 | mWaitingForRelease = false; 316 | mLastHoldRelease = System.currentTimeMillis(); 317 | managmentCommand("hold release\n"); 318 | managmentCommand("bytecount " + mBytecountInterval + "\n"); 319 | managmentCommand("state on\n"); 320 | //managmentCommand("log on all\n"); 321 | } 322 | 323 | public void releaseHold() { 324 | if (mWaitingForRelease) releaseHoldCmd(); 325 | } 326 | 327 | private void processProxyCMD(String argument) { 328 | String[] args = argument.split(",", 3); 329 | SocketAddress proxyaddr = ProxyDetection.detectProxy(mProfile); 330 | if (args.length >= 2) { 331 | String proto = args[1]; 332 | if (proto.equals("UDP")) { 333 | proxyaddr = null; 334 | } 335 | } 336 | if (proxyaddr instanceof InetSocketAddress) { 337 | InetSocketAddress isa = (InetSocketAddress) proxyaddr; 338 | VpnStatus.logInfo(R.string.using_proxy, isa.getHostName(), isa.getPort()); 339 | String proxycmd = String.format(Locale.ENGLISH, "proxy HTTP %s %d\n", isa.getHostName(), isa.getPort()); 340 | managmentCommand(proxycmd); 341 | } else { 342 | managmentCommand("proxy NONE\n"); 343 | } 344 | } 345 | 346 | private void processState(String argument) { 347 | String[] args = argument.split(",", 3); 348 | String currentstate = args[1]; 349 | if (args[2].equals(",,")) VpnStatus.updateStateString(currentstate, ""); 350 | else VpnStatus.updateStateString(currentstate, args[2]); 351 | } 352 | 353 | private void processByteCount(String argument) { 354 | // >BYTECOUNT:{BYTES_IN},{BYTES_OUT} 355 | int comma = argument.indexOf(','); 356 | long in = Long.parseLong(argument.substring(0, comma)); 357 | long out = Long.parseLong(argument.substring(comma + 1)); 358 | VpnStatus.updateByteCount(in, out); 359 | } 360 | 361 | private void processNeedCommand(String argument) { 362 | int p1 = argument.indexOf('\''); 363 | int p2 = argument.indexOf('\'', p1 + 1); 364 | String needed = argument.substring(p1 + 1, p2); 365 | String extra = argument.split(":", 2)[1]; 366 | String status = "ok"; 367 | switch (needed) { 368 | case "PROTECTFD": 369 | FileDescriptor fdtoprotect = mFDList.pollFirst(); 370 | protectFileDescriptor(fdtoprotect); 371 | break; 372 | case "DNSSERVER": 373 | mOpenVPNService.addDNS(extra); 374 | break; 375 | case "DNSDOMAIN": 376 | mOpenVPNService.setDomain(extra); 377 | break; 378 | case "ROUTE": { 379 | String[] routeparts = extra.split(" "); 380 | /* 381 | buf_printf (&out, "%s %s %s dev %s", network, netmask, gateway, rgi->iface); 382 | else 383 | buf_printf (&out, "%s %s %s", network, netmask, gateway); 384 | */ 385 | if (routeparts.length == 5) { 386 | if (BuildConfig.DEBUG) Assert.assertEquals("dev", routeparts[3]); 387 | mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], routeparts[4]); 388 | } else if (routeparts.length >= 3) { 389 | mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], null); 390 | } else { 391 | VpnStatus.logError("Unrecognized ROUTE cmd:" + Arrays.toString(routeparts) + " | " + argument); 392 | } 393 | break; 394 | } 395 | case "ROUTE6": { 396 | String[] routeparts = extra.split(" "); 397 | mOpenVPNService.addRoutev6(routeparts[0], routeparts[1]); 398 | break; 399 | } 400 | case "IFCONFIG": 401 | String[] ifconfigparts = extra.split(" "); 402 | int mtu = Integer.parseInt(ifconfigparts[2]); 403 | mOpenVPNService.setLocalIP(ifconfigparts[0], ifconfigparts[1], mtu, ifconfigparts[3]); 404 | break; 405 | case "IFCONFIG6": 406 | mOpenVPNService.setLocalIPv6(extra); 407 | break; 408 | case "PERSIST_TUN_ACTION": 409 | // check if tun cfg stayed the same 410 | status = mOpenVPNService.getTunReopenStatus(); 411 | break; 412 | case "OPENTUN": 413 | if (sendTunFD(needed, extra)) return; 414 | else status = "cancel"; 415 | // This not nice or anything but setFileDescriptors accepts only FilDescriptor class :( 416 | break; 417 | default: 418 | Log.e(TAG, "Unknown needok command " + argument); 419 | return; 420 | } 421 | String cmd = String.format("needok '%s' %s\n", needed, status); 422 | managmentCommand(cmd); 423 | } 424 | 425 | private boolean sendTunFD(String needed, String extra) { 426 | if (!extra.equals("tun")) { 427 | // We only support tun 428 | VpnStatus.logError(String.format("Device type %s requested, but only tun is possible with the Android API, sorry!", extra)); 429 | return false; 430 | } 431 | ParcelFileDescriptor pfd = mOpenVPNService.openTun(); 432 | if (pfd == null) return false; 433 | Method setInt; 434 | int fdint = pfd.getFd(); 435 | try { 436 | setInt = FileDescriptor.class.getDeclaredMethod("setInt$", int.class); 437 | FileDescriptor fdtosend = new FileDescriptor(); 438 | setInt.invoke(fdtosend, fdint); 439 | FileDescriptor[] fds = {fdtosend}; 440 | mSocket.setFileDescriptorsForSend(fds); 441 | // Trigger a send so we can close the fd on our side of the channel 442 | // The API documentation fails to mention that it will not reset the file descriptor to 443 | // be send and will happily send the file descriptor on every write ... 444 | String cmd = String.format("needok '%s' %s\n", needed, "ok"); 445 | managmentCommand(cmd); 446 | // Set the FileDescriptor to null to stop this mad behavior 447 | mSocket.setFileDescriptorsForSend(null); 448 | pfd.close(); 449 | return true; 450 | } catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException | 451 | IOException | IllegalAccessException exp) { 452 | VpnStatus.logException("Could not send fd over socket", exp); 453 | } 454 | return false; 455 | } 456 | 457 | private void processPWCommand(String argument) { 458 | //argument has the form Need 'Private Key' password 459 | // or ">PASSWORD:Verification Failed: '%s' ['%s']" 460 | String needed; 461 | try { 462 | int p1 = argument.indexOf('\''); 463 | int p2 = argument.indexOf('\'', p1 + 1); 464 | needed = argument.substring(p1 + 1, p2); 465 | if (argument.startsWith("Verification Failed")) { 466 | proccessPWFailed(needed, argument.substring(p2 + 1)); 467 | return; 468 | } 469 | } catch (StringIndexOutOfBoundsException sioob) { 470 | VpnStatus.logError("Could not parse management Password command: " + argument); 471 | return; 472 | } 473 | String pw = null; 474 | if (needed.equals("Private Key")) { 475 | pw = mProfile.getPasswordPrivateKey(); 476 | } else if (needed.equals("Auth")) { 477 | String usercmd = String.format("username '%s' %s\n", needed, VpnProfile.openVpnEscape(mProfile.mUsername)); 478 | managmentCommand(usercmd); 479 | pw = mProfile.getPasswordAuth(); 480 | } 481 | if (pw != null) { 482 | String cmd = String.format("password '%s' %s\n", needed, VpnProfile.openVpnEscape(pw)); 483 | managmentCommand(cmd); 484 | } else { 485 | VpnStatus.logError(String.format("Openvpn requires Authentication type '%s' but no password/key information available", needed)); 486 | } 487 | } 488 | 489 | private void proccessPWFailed(String needed, String args) { 490 | VpnStatus.updateStateString("AUTH_FAILED", needed + args, R.string.state_auth_failed, ConnectionStatus.LEVEL_AUTH_FAILED); 491 | } 492 | 493 | @Override 494 | public void networkChange(boolean samenetwork) { 495 | if (mWaitingForRelease) releaseHold(); 496 | else if (samenetwork) managmentCommand("network-change samenetwork\n"); 497 | else managmentCommand("network-change\n"); 498 | } 499 | 500 | @Override 501 | public void setPauseCallback(PausedStateCallback callback) { 502 | mPauseCallback = callback; 503 | } 504 | 505 | public void signalusr1() { 506 | mResumeHandler.removeCallbacks(mResumeHoldRunnable); 507 | if (!mWaitingForRelease) managmentCommand("signal SIGUSR1\n"); 508 | else 509 | // If signalusr1 is called update the state string 510 | // if there is another for stopping 511 | VpnStatus.updateStatePause(lastPauseReason); 512 | } 513 | 514 | public void reconnect() { 515 | signalusr1(); 516 | releaseHold(); 517 | } 518 | 519 | private void processSignCommand(String b64data) { 520 | String signed_string = mProfile.getSignedData(b64data); 521 | if (signed_string == null) { 522 | managmentCommand("rsa-sig\n"); 523 | managmentCommand("\nEND\n"); 524 | stopOpenVPN(); 525 | return; 526 | } 527 | managmentCommand("rsa-sig\n"); 528 | managmentCommand(signed_string); 529 | managmentCommand("\nEND\n"); 530 | } 531 | 532 | @Override 533 | public void pause(pauseReason reason) { 534 | lastPauseReason = reason; 535 | signalusr1(); 536 | } 537 | 538 | @Override 539 | public void resume() { 540 | releaseHold(); 541 | /* Reset the reason why we are disconnected */ 542 | lastPauseReason = pauseReason.noNetwork; 543 | } 544 | 545 | @Override 546 | public boolean stopVPN(boolean replaceConnection) { 547 | boolean stopSucceed = stopOpenVPN(); 548 | if (stopSucceed) { 549 | mShuttingDown = true; 550 | } 551 | return stopSucceed; 552 | } 553 | } 554 | -------------------------------------------------------------------------------- /vpn/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | package de.blinkt.openvpn.core;/* 6 | * This software is provided 'as-is', without any express or implied 7 | * warranty. In no event will Google be held liable for any damages 8 | * arising from the use of this software. 9 | * 10 | * Permission is granted to anyone to use this software for any purpose, 11 | * including commercial applications, and to alter it and redistribute it 12 | * freely, as long as the origin is not misrepresented. 13 | */ 14 | 15 | import android.os.Build; 16 | import android.os.Process; 17 | import android.util.Log; 18 | 19 | import java.io.ByteArrayOutputStream; 20 | import java.io.DataInputStream; 21 | import java.io.DataOutputStream; 22 | import java.io.File; 23 | import java.io.FileInputStream; 24 | import java.io.FileOutputStream; 25 | import java.io.IOException; 26 | import java.io.OutputStream; 27 | import java.io.UnsupportedEncodingException; 28 | import java.security.NoSuchAlgorithmException; 29 | import java.security.Provider; 30 | import java.security.SecureRandom; 31 | import java.security.SecureRandomSpi; 32 | import java.security.Security; 33 | 34 | /** 35 | * Fixes for the output of the default PRNG having low entropy. 36 | *

37 | * The fixes need to be applied via {@link #apply()} before any use of Java 38 | * Cryptography Architecture primitives. A good place to invoke them is in the 39 | * application's {@code onCreate}. 40 | */ 41 | public final class PRNGFixes { 42 | private static final int VERSION_CODE_JELLY_BEAN = 16; 43 | private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18; 44 | private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial(); 45 | 46 | /** 47 | * Hidden constructor to prevent instantiation. 48 | */ 49 | private PRNGFixes() { 50 | } 51 | 52 | /** 53 | * Applies all fixes. 54 | * 55 | * @throws SecurityException if a fix is needed but could not be applied. 56 | */ 57 | public static void apply() { 58 | applyOpenSSLFix(); 59 | installLinuxPRNGSecureRandom(); 60 | } 61 | 62 | /** 63 | * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the 64 | * fix is not needed. 65 | * 66 | * @throws SecurityException if the fix is needed but could not be applied. 67 | */ 68 | private static void applyOpenSSLFix() throws SecurityException { 69 | if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN) || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) { 70 | // No need to apply the fix 71 | return; 72 | } 73 | try { 74 | // Mix in the device- and invocation-specific seed. 75 | Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto").getMethod("RAND_seed", byte[].class).invoke(null, generateSeed()); 76 | // Mix output of Linux PRNG into OpenSSL's PRNG 77 | int bytesRead = (Integer) Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto").getMethod("RAND_load_file", String.class, long.class).invoke(null, "/dev/urandom", 1024); 78 | if (bytesRead != 1024) { 79 | throw new IOException("Unexpected number of bytes read from Linux PRNG: " + bytesRead); 80 | } 81 | } catch (Exception e) { 82 | throw new SecurityException("Failed to seed OpenSSL PRNG", e); 83 | } 84 | } 85 | 86 | /** 87 | * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the 88 | * default. Does nothing if the implementation is already the default or if 89 | * there is not need to install the implementation. 90 | * 91 | * @throws SecurityException if the fix is needed but could not be applied. 92 | */ 93 | private static void installLinuxPRNGSecureRandom() throws SecurityException { 94 | if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) { 95 | // No need to apply the fix 96 | return; 97 | } 98 | // Install a Linux PRNG-based SecureRandom implementation as the 99 | // default, if not yet installed. 100 | Provider[] secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG"); 101 | if ((secureRandomProviders == null) || (secureRandomProviders.length < 1) || (!LinuxPRNGSecureRandomProvider.class.equals(secureRandomProviders[0].getClass()))) { 102 | Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1); 103 | } 104 | // Assert that new SecureRandom() and 105 | // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed 106 | // by the Linux PRNG-based SecureRandom implementation. 107 | SecureRandom rng1 = new SecureRandom(); 108 | if (!LinuxPRNGSecureRandomProvider.class.equals(rng1.getProvider().getClass())) { 109 | throw new SecurityException("new SecureRandom() backed by wrong Provider: " + rng1.getProvider().getClass()); 110 | } 111 | SecureRandom rng2; 112 | try { 113 | rng2 = SecureRandom.getInstance("SHA1PRNG"); 114 | } catch (NoSuchAlgorithmException e) { 115 | throw new SecurityException("SHA1PRNG not available", e); 116 | } 117 | if (!LinuxPRNGSecureRandomProvider.class.equals(rng2.getProvider().getClass())) { 118 | throw new SecurityException("SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: " + rng2.getProvider().getClass()); 119 | } 120 | } 121 | 122 | /** 123 | * Generates a device- and invocation-specific seed to be mixed into the 124 | * Linux PRNG. 125 | */ 126 | private static byte[] generateSeed() { 127 | try { 128 | ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); 129 | DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer); 130 | seedBufferOut.writeLong(System.currentTimeMillis()); 131 | seedBufferOut.writeLong(System.nanoTime()); 132 | seedBufferOut.writeInt(Process.myPid()); 133 | seedBufferOut.writeInt(Process.myUid()); 134 | seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL); 135 | seedBufferOut.close(); 136 | return seedBuffer.toByteArray(); 137 | } catch (IOException e) { 138 | throw new SecurityException("Failed to generate seed", e); 139 | } 140 | } 141 | 142 | /** 143 | * Gets the hardware serial number of this device. 144 | * 145 | * @return serial number or {@code null} if not available. 146 | */ 147 | private static String getDeviceSerialNumber() { 148 | // We're using the Reflection API because Build.SERIAL is only available 149 | // since API Level 9 (Gingerbread, Android 2.3). 150 | try { 151 | return (String) Build.class.getField("SERIAL").get(null); 152 | } catch (Exception ignored) { 153 | return null; 154 | } 155 | } 156 | 157 | private static byte[] getBuildFingerprintAndDeviceSerial() { 158 | StringBuilder result = new StringBuilder(); 159 | String fingerprint = Build.FINGERPRINT; 160 | if (fingerprint != null) { 161 | result.append(fingerprint); 162 | } 163 | String serial = getDeviceSerialNumber(); 164 | if (serial != null) { 165 | result.append(serial); 166 | } 167 | try { 168 | return result.toString().getBytes("UTF-8"); 169 | } catch (UnsupportedEncodingException e) { 170 | throw new RuntimeException("UTF-8 encoding not supported"); 171 | } 172 | } 173 | 174 | /** 175 | * {@code Provider} of {@code SecureRandom} engines which pass through 176 | * all requests to the Linux PRNG. 177 | */ 178 | private static class LinuxPRNGSecureRandomProvider extends Provider { 179 | public LinuxPRNGSecureRandomProvider() { 180 | super("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses" + " /dev/urandom"); 181 | // Although /dev/urandom is not a SHA-1 PRNG, some apps 182 | // explicitly request a SHA1PRNG SecureRandom and we thus need to 183 | // prevent them from getting the default implementation whose output 184 | // may have low entropy. 185 | put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName()); 186 | put("SecureRandom.SHA1PRNG ImplementedIn", "Software"); 187 | } 188 | } 189 | 190 | /** 191 | * {@link SecureRandomSpi} which passes all requests to the Linux PRNG 192 | * ({@code /dev/urandom}). 193 | */ 194 | public static class LinuxPRNGSecureRandom extends SecureRandomSpi { 195 | /* 196 | * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed 197 | * are passed through to the Linux PRNG (/dev/urandom). Instances of 198 | * this class seed themselves by mixing in the current time, PID, UID, 199 | * build fingerprint, and hardware serial number (where available) into 200 | * Linux PRNG. 201 | * 202 | * Concurrency: Read requests to the underlying Linux PRNG are 203 | * serialized (on sLock) to ensure that multiple threads do not get 204 | * duplicated PRNG output. 205 | */ 206 | private static final File URANDOM_FILE = new File("/dev/urandom"); 207 | private static final Object sLock = new Object(); 208 | /** 209 | * Input stream for reading from Linux PRNG or {@code null} if not yet 210 | * opened. 211 | * 212 | * @GuardedBy("sLock") 213 | */ 214 | private static DataInputStream sUrandomIn; 215 | /** 216 | * Output stream for writing to Linux PRNG or {@code null} if not yet 217 | * opened. 218 | * 219 | * @GuardedBy("sLock") 220 | */ 221 | private static OutputStream sUrandomOut; 222 | /** 223 | * Whether this engine instance has been seeded. This is needed because 224 | * each instance needs to seed itself if the client does not explicitly 225 | * seed it. 226 | */ 227 | private boolean mSeeded; 228 | 229 | @Override 230 | protected void engineSetSeed(byte[] bytes) { 231 | try { 232 | OutputStream out; 233 | synchronized (sLock) { 234 | out = getUrandomOutputStream(); 235 | } 236 | out.write(bytes); 237 | out.flush(); 238 | } catch (IOException e) { 239 | // On a small fraction of devices /dev/urandom is not writable. 240 | // Log and ignore. 241 | Log.w(PRNGFixes.class.getSimpleName(), "Failed to mix seed into " + URANDOM_FILE); 242 | } finally { 243 | mSeeded = true; 244 | } 245 | } 246 | 247 | @Override 248 | protected void engineNextBytes(byte[] bytes) { 249 | if (!mSeeded) { 250 | // Mix in the device- and invocation-specific seed. 251 | engineSetSeed(generateSeed()); 252 | } 253 | try { 254 | DataInputStream in; 255 | synchronized (sLock) { 256 | in = getUrandomInputStream(); 257 | } 258 | synchronized (in) { 259 | in.readFully(bytes); 260 | } 261 | } catch (IOException e) { 262 | throw new SecurityException("Failed to read from " + URANDOM_FILE, e); 263 | } 264 | } 265 | 266 | @Override 267 | protected byte[] engineGenerateSeed(int size) { 268 | byte[] seed = new byte[size]; 269 | engineNextBytes(seed); 270 | return seed; 271 | } 272 | 273 | private DataInputStream getUrandomInputStream() { 274 | synchronized (sLock) { 275 | if (sUrandomIn == null) { 276 | // NOTE: Consider inserting a BufferedInputStream between 277 | // DataInputStream and FileInputStream if you need higher 278 | // PRNG output performance and can live with future PRNG 279 | // output being pulled into this process prematurely. 280 | try { 281 | sUrandomIn = new DataInputStream(new FileInputStream(URANDOM_FILE)); 282 | } catch (IOException e) { 283 | throw new SecurityException("Failed to open " + URANDOM_FILE + " for reading", e); 284 | } 285 | } 286 | return sUrandomIn; 287 | } 288 | } 289 | 290 | private OutputStream getUrandomOutputStream() throws IOException { 291 | synchronized (sLock) { 292 | if (sUrandomOut == null) { 293 | sUrandomOut = new FileOutputStream(URANDOM_FILE); 294 | } 295 | return sUrandomOut; 296 | } 297 | } 298 | } 299 | } -------------------------------------------------------------------------------- /vpn/src/main/java/de/blinkt/openvpn/core/ProfileManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | package de.blinkt.openvpn.core; 6 | 7 | import android.app.Activity; 8 | import android.content.Context; 9 | import android.content.SharedPreferences; 10 | import android.content.SharedPreferences.Editor; 11 | import android.preference.PreferenceManager; 12 | 13 | import java.io.IOException; 14 | import java.io.ObjectInputStream; 15 | import java.io.ObjectOutputStream; 16 | import java.util.Collection; 17 | import java.util.HashMap; 18 | import java.util.HashSet; 19 | import java.util.Set; 20 | 21 | import de.blinkt.openvpn.VpnProfile; 22 | 23 | public class ProfileManager { 24 | private static final String PREFS_NAME = "VPNList"; 25 | private static final String LAST_CONNECTED_PROFILE = "lastConnectedProfile"; 26 | private static ProfileManager instance; 27 | private static VpnProfile mLastConnectedVpn = null; 28 | private static VpnProfile tmpprofile = null; 29 | private HashMap profiles = new HashMap<>(); 30 | 31 | private ProfileManager() { 32 | } 33 | 34 | private static VpnProfile get(String key) { 35 | if (tmpprofile != null && tmpprofile.getUUIDString().equals(key)) { 36 | return tmpprofile; 37 | } 38 | if (instance == null) { 39 | return null; 40 | } 41 | return instance.profiles.get(key); 42 | } 43 | 44 | private static void checkInstance(Context context) { 45 | if (instance == null) { 46 | instance = new ProfileManager(); 47 | instance.loadVPNList(context); 48 | } 49 | } 50 | 51 | synchronized public static ProfileManager getInstance(Context context) { 52 | checkInstance(context); 53 | return instance; 54 | } 55 | 56 | public static void setConntectedVpnProfileDisconnected(Context c) { 57 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); 58 | Editor prefsedit = prefs.edit(); 59 | prefsedit.putString(LAST_CONNECTED_PROFILE, null); 60 | prefsedit.apply(); 61 | } 62 | 63 | /** 64 | * Sets the profile that is connected (to connect if the service restarts) 65 | */ 66 | static void setConnectedVpnProfile(Context c, VpnProfile connectedProfile) { 67 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); 68 | Editor prefsedit = prefs.edit(); 69 | prefsedit.putString(LAST_CONNECTED_PROFILE, connectedProfile.getUUIDString()); 70 | prefsedit.apply(); 71 | mLastConnectedVpn = connectedProfile; 72 | } 73 | 74 | /** 75 | * Returns the profile that was last connected (to connect if the service restarts) 76 | */ 77 | static VpnProfile getLastConnectedProfile(Context c) { 78 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); 79 | String lastConnectedProfile = prefs.getString(LAST_CONNECTED_PROFILE, null); 80 | if (lastConnectedProfile != null) { 81 | return get(c, lastConnectedProfile); 82 | } else { 83 | return null; 84 | } 85 | } 86 | 87 | public static void setTemporaryProfile(VpnProfile tmp) { 88 | ProfileManager.tmpprofile = tmp; 89 | } 90 | 91 | public static boolean isTempProfile() { 92 | return mLastConnectedVpn == tmpprofile; 93 | } 94 | 95 | public static VpnProfile get(Context context, String profileUUID) { 96 | checkInstance(context); 97 | return get(profileUUID); 98 | } 99 | 100 | static VpnProfile getLastConnectedVpn() { 101 | return mLastConnectedVpn; 102 | } 103 | 104 | static VpnProfile getAlwaysOnVPN(Context context) { 105 | checkInstance(context); 106 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 107 | String uuid = prefs.getString("alwaysOnVpn", null); 108 | return get(uuid); 109 | } 110 | 111 | public Collection getProfiles() { 112 | return profiles.values(); 113 | } 114 | 115 | public VpnProfile getProfileByName(String name) { 116 | for (VpnProfile vpnp : profiles.values()) { 117 | if (vpnp.getName().equals(name)) { 118 | return vpnp; 119 | } 120 | } 121 | return null; 122 | } 123 | 124 | private void saveProfileList(Context context) { 125 | SharedPreferences sharedprefs = context.getSharedPreferences(PREFS_NAME, Activity.MODE_PRIVATE); 126 | Editor editor = sharedprefs.edit(); 127 | editor.putStringSet("vpnlist", profiles.keySet()); 128 | // For reasing I do not understand at all 129 | // Android saves my prefs file only one time 130 | // if I remove the debug code below :( 131 | int counter = sharedprefs.getInt("counter", 0); 132 | editor.putInt("counter", counter + 1); 133 | editor.apply(); 134 | } 135 | 136 | public void addProfile(VpnProfile profile) { 137 | profiles.put(profile.getUUID().toString(), profile); 138 | } 139 | 140 | public void saveProfile(Context context, VpnProfile profile) { 141 | ObjectOutputStream vpnfile; 142 | try { 143 | vpnfile = new ObjectOutputStream(context.openFileOutput((profile.getUUID().toString() + ".vp"), Activity.MODE_PRIVATE)); 144 | vpnfile.writeObject(profile); 145 | vpnfile.flush(); 146 | vpnfile.close(); 147 | } catch (IOException e) { 148 | VpnStatus.logException("saving VPN profile", e); 149 | throw new RuntimeException(e); 150 | } 151 | } 152 | 153 | private void loadVPNList(Context context) { 154 | profiles = new HashMap<>(); 155 | SharedPreferences listpref = context.getSharedPreferences(PREFS_NAME, Activity.MODE_PRIVATE); 156 | Set vlist = listpref.getStringSet("vpnlist", null); 157 | if (vlist == null) { 158 | vlist = new HashSet<>(); 159 | } 160 | for (String vpnentry : vlist) { 161 | try { 162 | ObjectInputStream vpnfile = new ObjectInputStream(context.openFileInput(vpnentry + ".vp")); 163 | VpnProfile vp = ((VpnProfile) vpnfile.readObject()); 164 | // Sanity check 165 | if (vp == null || vp.mName == null || vp.getUUID() == null) continue; 166 | vp.upgradeProfile(); 167 | profiles.put(vp.getUUID().toString(), vp); 168 | } catch (IOException | ClassNotFoundException e) { 169 | VpnStatus.logException("Loading VPN List", e); 170 | } 171 | } 172 | } 173 | 174 | public void removeProfile(Context context, VpnProfile profile) { 175 | String vpnentry = profile.getUUID().toString(); 176 | profiles.remove(vpnentry); 177 | saveProfileList(context); 178 | context.deleteFile(vpnentry + ".vp"); 179 | if (mLastConnectedVpn == profile) mLastConnectedVpn = null; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /vpn/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | package de.blinkt.openvpn.core; 6 | 7 | import java.net.InetSocketAddress; 8 | import java.net.MalformedURLException; 9 | import java.net.Proxy; 10 | import java.net.ProxySelector; 11 | import java.net.SocketAddress; 12 | import java.net.URISyntaxException; 13 | import java.net.URL; 14 | import java.util.List; 15 | 16 | import de.blinkt.openvpn.R; 17 | import de.blinkt.openvpn.VpnProfile; 18 | 19 | public class ProxyDetection { 20 | static SocketAddress detectProxy(VpnProfile vp) { 21 | // Construct a new url with https as protocol 22 | try { 23 | URL url = new URL(String.format("https://%s:%s", vp.mServerName, vp.mServerPort)); 24 | Proxy proxy = getFirstProxy(url); 25 | if (proxy == null) return null; 26 | SocketAddress addr = proxy.address(); 27 | if (addr instanceof InetSocketAddress) { 28 | return addr; 29 | } 30 | } catch (MalformedURLException e) { 31 | VpnStatus.logError(R.string.getproxy_error, e.getLocalizedMessage()); 32 | } catch (URISyntaxException e) { 33 | VpnStatus.logError(R.string.getproxy_error, e.getLocalizedMessage()); 34 | } 35 | return null; 36 | } 37 | 38 | static Proxy getFirstProxy(URL url) throws URISyntaxException { 39 | System.setProperty("java.net.useSystemProxies", "true"); 40 | List proxylist = ProxySelector.getDefault().select(url.toURI()); 41 | if (proxylist != null) { 42 | for (Proxy proxy : proxylist) { 43 | SocketAddress addr = proxy.address(); 44 | if (addr != null) { 45 | return proxy; 46 | } 47 | } 48 | } 49 | return null; 50 | } 51 | } -------------------------------------------------------------------------------- /vpn/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | package de.blinkt.openvpn.core; 6 | 7 | import android.annotation.TargetApi; 8 | import android.content.Context; 9 | import android.content.Intent; 10 | import android.os.Build; 11 | 12 | import java.io.File; 13 | import java.io.FileOutputStream; 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | import java.util.Arrays; 17 | import java.util.Vector; 18 | 19 | import de.blinkt.openvpn.R; 20 | import de.blinkt.openvpn.VpnProfile; 21 | 22 | public class VPNLaunchHelper { 23 | private static final String MININONPIEVPN = "nopie_openvpn"; 24 | private static final String MINIPIEVPN = "pie_openvpn"; 25 | private static final String OVPNCONFIGFILE = "android.conf"; 26 | 27 | private static String writeMiniVPN(Context context) { 28 | String[] abis; 29 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 30 | abis = getSupportedABIsLollipop(); 31 | } else { 32 | //noinspection deprecation 33 | abis = new String[]{Build.CPU_ABI, Build.CPU_ABI2}; 34 | } 35 | String nativeAPI = NativeUtils.getNativeAPI(); 36 | if (!nativeAPI.equals(abis[0])) { 37 | VpnStatus.logWarning(R.string.abi_mismatch, Arrays.toString(abis), nativeAPI); 38 | abis = new String[]{nativeAPI}; 39 | } 40 | for (String abi : abis) { 41 | File vpnExecutable = new File(context.getCacheDir(), getMiniVPNExecutableName() + "." + abi); 42 | if ((vpnExecutable.exists() && vpnExecutable.canExecute()) || writeMiniVPNBinary(context, abi, vpnExecutable)) { 43 | return vpnExecutable.getPath(); 44 | } 45 | } 46 | return null; 47 | } 48 | 49 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 50 | private static String[] getSupportedABIsLollipop() { 51 | return Build.SUPPORTED_ABIS; 52 | } 53 | 54 | private static String getMiniVPNExecutableName() { 55 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return MINIPIEVPN; 56 | else return MININONPIEVPN; 57 | } 58 | 59 | static String[] replacePieWithNoPie(String[] mArgv) { 60 | mArgv[0] = mArgv[0].replace(MINIPIEVPN, MININONPIEVPN); 61 | return mArgv; 62 | } 63 | 64 | static String[] buildOpenvpnArgv(Context c) { 65 | Vector args = new Vector<>(); 66 | String binaryName = writeMiniVPN(c); 67 | // Add fixed paramenters 68 | //args.add("/data/data/de.blinkt.openvpn/lib/openvpn"); 69 | if (binaryName == null) { 70 | VpnStatus.logError("Error writing minivpn binary"); 71 | return null; 72 | } 73 | args.add(binaryName); 74 | args.add("--config"); 75 | args.add(getConfigFilePath(c)); 76 | return args.toArray(new String[args.size()]); 77 | } 78 | 79 | private static boolean writeMiniVPNBinary(Context context, String abi, File mvpnout) { 80 | try { 81 | InputStream mvpn; 82 | try { 83 | mvpn = context.getAssets().open(getMiniVPNExecutableName() + "." + abi); 84 | } catch (IOException errabi) { 85 | VpnStatus.logInfo("Failed getting assets for archicture " + abi); 86 | return false; 87 | } 88 | FileOutputStream fout = new FileOutputStream(mvpnout); 89 | byte buf[] = new byte[4096]; 90 | int lenread = mvpn.read(buf); 91 | while (lenread > 0) { 92 | fout.write(buf, 0, lenread); 93 | lenread = mvpn.read(buf); 94 | } 95 | fout.close(); 96 | if (!mvpnout.setExecutable(true)) { 97 | VpnStatus.logError("Failed to make OpenVPN executable"); 98 | return false; 99 | } 100 | return true; 101 | } catch (IOException e) { 102 | VpnStatus.logException(e); 103 | return false; 104 | } 105 | } 106 | 107 | public static void startOpenVpn(VpnProfile startprofile, Context context) { 108 | Intent startVPN = startprofile.getStartServiceIntent(context); 109 | if (startVPN != null) { 110 | context.startService(startVPN); 111 | } 112 | } 113 | 114 | public static String getConfigFilePath(Context context) { 115 | return context.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /vpn/src/main/java/de/blinkt/openvpn/core/VpnStatus.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | package de.blinkt.openvpn.core; 6 | 7 | import android.content.Context; 8 | import android.os.Build; 9 | import android.os.HandlerThread; 10 | import android.os.Message; 11 | 12 | import java.io.File; 13 | import java.io.PrintWriter; 14 | import java.io.StringWriter; 15 | import java.util.LinkedList; 16 | import java.util.Locale; 17 | import java.util.Vector; 18 | 19 | import de.blinkt.openvpn.R; 20 | 21 | public class VpnStatus { 22 | // keytool -printcert -jarfile de.blinkt.openvpn_85.apk 23 | public static final byte[] officalkey = {-58, -42, -44, -106, 90, -88, -87, -88, -52, -124, 84, 117, 66, 79, -112, -111, -46, 86, -37, 109}; 24 | public static final byte[] officaldebugkey = {-99, -69, 45, 71, 114, -116, 82, 66, -99, -122, 50, -70, -56, -111, 98, -35, -65, 105, 82, 43}; 25 | public static final byte[] amazonkey = {-116, -115, -118, -89, -116, -112, 120, 55, 79, -8, -119, -23, 106, -114, -85, -56, -4, 105, 26, -57}; 26 | public static final byte[] fdroidkey = {-92, 111, -42, -46, 123, -96, -60, 79, -27, -31, 49, 103, 11, -54, -68, -27, 17, 2, 121, 104}; 27 | static final int MAXLOGENTRIES = 1000; 28 | public static LinkedList logbuffer; 29 | private static Vector logListener; 30 | private static Vector stateListener; 31 | private static Vector byteCountListener; 32 | private static String mLaststatemsg = ""; 33 | private static String mLaststate = "NOPROCESS"; 34 | private static int mLastStateresid = R.string.state_noprocess; 35 | private static long mlastByteCount[] = {0, 0, 0, 0}; 36 | private static HandlerThread mHandlerThread; 37 | private static ConnectionStatus mLastLevel = ConnectionStatus.LEVEL_NOTCONNECTED; 38 | private static LogFileHandler mLogFileHandler; 39 | 40 | static { 41 | logbuffer = new LinkedList<>(); 42 | logListener = new Vector<>(); 43 | stateListener = new Vector<>(); 44 | byteCountListener = new Vector<>(); 45 | logInformation(); 46 | } 47 | 48 | public static void logException(LogLevel ll, String context, Exception e) { 49 | StringWriter sw = new StringWriter(); 50 | e.printStackTrace(new PrintWriter(sw)); 51 | LogItem li; 52 | if (context != null) { 53 | li = new LogItem(ll, R.string.unhandled_exception_context, e.getMessage(), sw.toString(), context); 54 | } else { 55 | li = new LogItem(ll, R.string.unhandled_exception, e.getMessage(), sw.toString()); 56 | } 57 | newLogItem(li); 58 | } 59 | 60 | public static void logException(Exception e) { 61 | logException(LogLevel.ERROR, null, e); 62 | } 63 | 64 | static void logException(String context, Exception e) { 65 | logException(LogLevel.ERROR, context, e); 66 | } 67 | 68 | public static boolean isVPNActive() { 69 | return mLastLevel != ConnectionStatus.LEVEL_AUTH_FAILED && !(mLastLevel == ConnectionStatus.LEVEL_NOTCONNECTED); 70 | } 71 | 72 | static String getLastCleanLogMessage(Context c) { 73 | String message = mLaststatemsg; 74 | switch (mLastLevel) { 75 | case LEVEL_CONNECTED: 76 | String[] parts = mLaststatemsg.split(","); 77 | /* 78 | (a) the integer unix date/time, 79 | (b) the state name, 80 | 0 (c) optional descriptive string (used mostly on RECONNECTING 81 | and EXITING to show the reason for the disconnect), 82 | 1 (d) optional TUN/TAP local IPv4 address 83 | 2 (e) optional address of remote server, 84 | 3 (f) optional port of remote server, 85 | 4 (g) optional local address, 86 | 5 (h) optional local port, and 87 | 6 (i) optional TUN/TAP local IPv6 address. 88 | */ 89 | // Return only the assigned IP addresses in the UI 90 | if (parts.length >= 7) { 91 | message = String.format(Locale.CHINA, "%s %s", parts[1], parts[6]); 92 | } 93 | break; 94 | } 95 | while (message.endsWith(",")) message = message.substring(0, message.length() - 1); 96 | String status = mLaststate; 97 | if (status.equals("NOPROCESS")) return message; 98 | if (mLastStateresid == R.string.state_waitconnectretry) { 99 | return c.getString(R.string.state_waitconnectretry, mLaststatemsg); 100 | } 101 | String prefix = c.getString(mLastStateresid); 102 | if (mLastStateresid == R.string.unknown_state) message = status + message; 103 | if (message.length() > 0) prefix += ": "; 104 | return prefix + message; 105 | } 106 | 107 | public static void initLogCache(File cacheDir) { 108 | mHandlerThread = new HandlerThread("LogFileWriter", Thread.MIN_PRIORITY); 109 | mHandlerThread.start(); 110 | mLogFileHandler = new LogFileHandler(mHandlerThread.getLooper()); 111 | Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_INIT, cacheDir); 112 | mLogFileHandler.sendMessage(m); 113 | } 114 | 115 | static void flushLog() { 116 | mLogFileHandler.sendEmptyMessage(LogFileHandler.FLUSH_TO_DISK); 117 | } 118 | 119 | public synchronized static void logMessage(LogLevel level, String prefix, String message) { 120 | newLogItem(new LogItem(level, prefix + message)); 121 | } 122 | 123 | public synchronized static void clearLog() { 124 | logbuffer.clear(); 125 | logInformation(); 126 | if (mLogFileHandler != null) mLogFileHandler.sendEmptyMessage(LogFileHandler.TRIM_LOG_FILE); 127 | } 128 | 129 | private static void logInformation() { 130 | String nativeAPI; 131 | try { 132 | nativeAPI = NativeUtils.getNativeAPI(); 133 | } catch (UnsatisfiedLinkError ignore) { 134 | nativeAPI = "error"; 135 | } 136 | logInfo(R.string.mobile_info, Build.MODEL, Build.BOARD, Build.BRAND, Build.VERSION.SDK_INT, nativeAPI, Build.VERSION.RELEASE, Build.ID, Build.FINGERPRINT, "", ""); 137 | } 138 | 139 | public synchronized static void addLogListener(LogListener ll) { 140 | logListener.add(ll); 141 | } 142 | 143 | public synchronized static void removeLogListener(LogListener ll) { 144 | logListener.remove(ll); 145 | } 146 | 147 | synchronized static void addByteCountListener(ByteCountListener bcl) { 148 | bcl.updateByteCount(mlastByteCount[0], mlastByteCount[1], mlastByteCount[2], mlastByteCount[3]); 149 | byteCountListener.add(bcl); 150 | } 151 | 152 | synchronized static void removeByteCountListener(ByteCountListener bcl) { 153 | byteCountListener.remove(bcl); 154 | } 155 | 156 | synchronized static void addStateListener(StateListener sl) { 157 | if (!stateListener.contains(sl)) { 158 | stateListener.add(sl); 159 | if (mLaststate != null) sl.updateState(mLaststate, mLaststatemsg, mLastStateresid, mLastLevel); 160 | } 161 | } 162 | 163 | private static int getLocalizedState(String state) { 164 | switch (state) { 165 | case "CONNECTING": 166 | return R.string.state_connecting; 167 | case "WAIT": 168 | return R.string.state_wait; 169 | case "AUTH": 170 | return R.string.state_auth; 171 | case "GET_CONFIG": 172 | return R.string.state_get_config; 173 | case "ASSIGN_IP": 174 | return R.string.state_assign_ip; 175 | case "ADD_ROUTES": 176 | return R.string.state_add_routes; 177 | case "CONNECTED": 178 | return R.string.state_connected; 179 | case "DISCONNECTED": 180 | return R.string.state_disconnected; 181 | case "RECONNECTING": 182 | return R.string.state_reconnecting; 183 | case "EXITING": 184 | return R.string.state_exiting; 185 | case "RESOLVE": 186 | return R.string.state_resolve; 187 | case "TCP_CONNECT": 188 | return R.string.state_tcp_connect; 189 | default: 190 | return R.string.unknown_state; 191 | } 192 | } 193 | 194 | static void updateStatePause(OpenVPNManagement.pauseReason pauseReason) { 195 | switch (pauseReason) { 196 | case noNetwork: 197 | VpnStatus.updateStateString("NONETWORK", "", R.string.state_nonetwork, ConnectionStatus.LEVEL_NONETWORK); 198 | break; 199 | case screenOff: 200 | VpnStatus.updateStateString("SCREENOFF", "", R.string.state_screenoff, ConnectionStatus.LEVEL_VPNPAUSED); 201 | break; 202 | case userPause: 203 | VpnStatus.updateStateString("USERPAUSE", "", R.string.state_userpause, ConnectionStatus.LEVEL_VPNPAUSED); 204 | break; 205 | } 206 | } 207 | 208 | private static ConnectionStatus getLevel(String state) { 209 | String[] noreplyet = {"CONNECTING", "WAIT", "RECONNECTING", "RESOLVE", "TCP_CONNECT"}; 210 | String[] reply = {"AUTH", "GET_CONFIG", "ASSIGN_IP", "ADD_ROUTES"}; 211 | String[] connected = {"CONNECTED"}; 212 | String[] notconnected = {"DISCONNECTED", "EXITING"}; 213 | for (String x : noreplyet) 214 | if (state.equals(x)) return ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET; 215 | for (String x : reply) 216 | if (state.equals(x)) return ConnectionStatus.LEVEL_CONNECTING_SERVER_REPLIED; 217 | for (String x : connected) 218 | if (state.equals(x)) return ConnectionStatus.LEVEL_CONNECTED; 219 | for (String x : notconnected) 220 | if (state.equals(x)) return ConnectionStatus.LEVEL_NOTCONNECTED; 221 | return ConnectionStatus.UNKNOWN_LEVEL; 222 | } 223 | 224 | synchronized static void removeStateListener(StateListener sl) { 225 | stateListener.remove(sl); 226 | } 227 | 228 | synchronized static LogItem[] getlogbuffer() { 229 | // The stoned way of java to return an array from a vector 230 | // brought to you by eclipse auto complete 231 | return logbuffer.toArray(new LogItem[logbuffer.size()]); 232 | } 233 | 234 | static void updateStateString(String state, String msg) { 235 | int rid = getLocalizedState(state); 236 | ConnectionStatus level = getLevel(state); 237 | updateStateString(state, msg, rid, level); 238 | } 239 | 240 | synchronized static void updateStateString(String state, String msg, int resid, ConnectionStatus level) { 241 | // Workound for OpenVPN doing AUTH and wait and being connected 242 | // Simply ignore these state 243 | if (mLastLevel == ConnectionStatus.LEVEL_CONNECTED && (state.equals("WAIT") || state.equals("AUTH"))) { 244 | newLogItem(new LogItem((LogLevel.DEBUG), String.format("Ignoring OpenVPN Status in CONNECTED state (%s->%s): %s", state, level.toString(), msg))); 245 | return; 246 | } 247 | mLaststate = state; 248 | mLaststatemsg = msg; 249 | mLastStateresid = resid; 250 | mLastLevel = level; 251 | for (StateListener sl : stateListener) { 252 | sl.updateState(state, msg, resid, level); 253 | } 254 | //newLogItem(new LogItem((LogLevel.DEBUG), String.format("New OpenVPN Status (%s->%s): %s",state,level.toString(),msg))); 255 | } 256 | 257 | static void logInfo(String message) { 258 | newLogItem(new LogItem(LogLevel.INFO, message)); 259 | } 260 | 261 | static void logDebug(String message) { 262 | newLogItem(new LogItem(LogLevel.DEBUG, message)); 263 | } 264 | 265 | static void logInfo(int resourceId, Object... args) { 266 | newLogItem(new LogItem(LogLevel.INFO, resourceId, args)); 267 | } 268 | 269 | static void logDebug(int resourceId, Object... args) { 270 | newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args)); 271 | } 272 | 273 | private static void newLogItem(LogItem logItem) { 274 | newLogItem(logItem, false); 275 | } 276 | 277 | synchronized static void newLogItem(LogItem logItem, boolean cachedLine) { 278 | if (cachedLine) { 279 | logbuffer.addFirst(logItem); 280 | } else { 281 | logbuffer.addLast(logItem); 282 | if (mLogFileHandler != null) { 283 | Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_MESSAGE, logItem); 284 | mLogFileHandler.sendMessage(m); 285 | } 286 | } 287 | if (logbuffer.size() > MAXLOGENTRIES + MAXLOGENTRIES / 2) { 288 | while (logbuffer.size() > MAXLOGENTRIES) logbuffer.removeFirst(); 289 | if (mLogFileHandler != null) mLogFileHandler.sendMessage(mLogFileHandler.obtainMessage(LogFileHandler.TRIM_LOG_FILE)); 290 | } 291 | //if (BuildConfig.DEBUG && !cachedLine && !BuildConfig.FLAVOR.equals("test")) 292 | // Log.d("OpenVPN", logItem.getString(null)); 293 | for (LogListener ll : logListener) { 294 | ll.newLog(logItem); 295 | } 296 | } 297 | 298 | public static void logError(String msg) { 299 | newLogItem(new LogItem(LogLevel.ERROR, msg)); 300 | } 301 | 302 | static void logWarning(int resourceId, Object... args) { 303 | newLogItem(new LogItem(LogLevel.WARNING, resourceId, args)); 304 | } 305 | 306 | static void logWarning(String msg) { 307 | newLogItem(new LogItem(LogLevel.WARNING, msg)); 308 | } 309 | 310 | public static void logError(int resourceId) { 311 | newLogItem(new LogItem(LogLevel.ERROR, resourceId)); 312 | } 313 | 314 | public static void logError(int resourceId, Object... args) { 315 | newLogItem(new LogItem(LogLevel.ERROR, resourceId, args)); 316 | } 317 | 318 | static void logMessageOpenVPN(LogLevel level, int ovpnlevel, String message) { 319 | newLogItem(new LogItem(level, ovpnlevel, message)); 320 | } 321 | 322 | static synchronized void updateByteCount(long in, long out) { 323 | long lastIn = mlastByteCount[0]; 324 | long lastOut = mlastByteCount[1]; 325 | long diffIn = mlastByteCount[2] = Math.max(0, in - lastIn); 326 | long diffOut = mlastByteCount[3] = Math.max(0, out - lastOut); 327 | mlastByteCount = new long[]{in, out, diffIn, diffOut}; 328 | for (ByteCountListener bcl : byteCountListener) { 329 | bcl.updateByteCount(in, out, diffIn, diffOut); 330 | } 331 | } 332 | 333 | enum ConnectionStatus { 334 | LEVEL_CONNECTED, 335 | LEVEL_VPNPAUSED, 336 | LEVEL_CONNECTING_SERVER_REPLIED, 337 | LEVEL_CONNECTING_NO_SERVER_REPLY_YET, 338 | LEVEL_NONETWORK, 339 | LEVEL_NOTCONNECTED, 340 | LEVEL_START, 341 | LEVEL_AUTH_FAILED, 342 | LEVEL_WAITING_FOR_USER_INPUT, 343 | UNKNOWN_LEVEL 344 | } 345 | 346 | public enum LogLevel { 347 | INFO(2), 348 | ERROR(-2), 349 | WARNING(1), 350 | VERBOSE(3), 351 | DEBUG(4); 352 | protected int mValue; 353 | 354 | LogLevel(int value) { 355 | mValue = value; 356 | } 357 | 358 | public static LogLevel getEnumByValue(int value) { 359 | switch (value) { 360 | case 1: 361 | return INFO; 362 | case 2: 363 | return ERROR; 364 | case 3: 365 | return WARNING; 366 | case 4: 367 | return DEBUG; 368 | default: 369 | return null; 370 | } 371 | } 372 | 373 | public int getInt() { 374 | return mValue; 375 | } 376 | } 377 | 378 | interface LogListener { 379 | void newLog(LogItem logItem); 380 | } 381 | 382 | interface StateListener { 383 | void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level); 384 | } 385 | 386 | interface ByteCountListener { 387 | void updateByteCount(long in, long out, long diffIn, long diffOut); 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /vpn/src/main/java/de/blinkt/openvpn/core/X509Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | package de.blinkt.openvpn.core; 6 | 7 | import android.content.Context; 8 | import android.content.res.Resources; 9 | import android.text.TextUtils; 10 | 11 | import org.spongycastle.util.io.pem.PemObject; 12 | import org.spongycastle.util.io.pem.PemReader; 13 | 14 | import java.io.ByteArrayInputStream; 15 | import java.io.File; 16 | import java.io.FileInputStream; 17 | import java.io.FileNotFoundException; 18 | import java.io.FileReader; 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | import java.io.Reader; 22 | import java.io.StringReader; 23 | import java.lang.reflect.InvocationTargetException; 24 | import java.lang.reflect.Method; 25 | import java.security.cert.Certificate; 26 | import java.security.cert.CertificateException; 27 | import java.security.cert.CertificateExpiredException; 28 | import java.security.cert.CertificateFactory; 29 | import java.security.cert.CertificateNotYetValidException; 30 | import java.security.cert.X509Certificate; 31 | import java.util.Date; 32 | import java.util.Hashtable; 33 | import java.util.Vector; 34 | 35 | import javax.security.auth.x500.X500Principal; 36 | 37 | import de.blinkt.openvpn.R; 38 | import de.blinkt.openvpn.VpnProfile; 39 | 40 | public class X509Utils { 41 | public static Certificate[] getCertificatesFromFile(String certfilename) throws FileNotFoundException, CertificateException { 42 | CertificateFactory certFact = CertificateFactory.getInstance("X.509"); 43 | Vector certificates = new Vector<>(); 44 | if (VpnProfile.isEmbedded(certfilename)) { 45 | int subIndex = certfilename.indexOf("-----BEGIN CERTIFICATE-----"); 46 | do { 47 | // The java certifcate reader is ... kind of stupid 48 | // It does NOT ignore chars before the --BEGIN ... 49 | subIndex = Math.max(0, subIndex); 50 | InputStream inStream = new ByteArrayInputStream(certfilename.substring(subIndex).getBytes()); 51 | certificates.add(certFact.generateCertificate(inStream)); 52 | subIndex = certfilename.indexOf("-----BEGIN CERTIFICATE-----", subIndex + 1); 53 | } while (subIndex > 0); 54 | return certificates.toArray(new Certificate[certificates.size()]); 55 | } else { 56 | InputStream inStream = new FileInputStream(certfilename); 57 | return new Certificate[]{certFact.generateCertificate(inStream)}; 58 | } 59 | } 60 | 61 | public static PemObject readPemObjectFromFile(String keyfilename) throws IOException { 62 | Reader inStream; 63 | if (VpnProfile.isEmbedded(keyfilename)) inStream = new StringReader(VpnProfile.getEmbeddedContent(keyfilename)); 64 | else inStream = new FileReader(new File(keyfilename)); 65 | PemReader pr = new PemReader(inStream); 66 | PemObject r = pr.readPemObject(); 67 | pr.close(); 68 | return r; 69 | } 70 | 71 | public static String getCertificateFriendlyName(Context c, String filename) { 72 | if (!TextUtils.isEmpty(filename)) { 73 | try { 74 | X509Certificate cert = (X509Certificate) getCertificatesFromFile(filename)[0]; 75 | String friendlycn = getCertificateFriendlyName(cert); 76 | friendlycn = getCertificateValidityString(cert, c.getResources()) + friendlycn; 77 | return friendlycn; 78 | } catch (Exception e) { 79 | VpnStatus.logError("Could not read certificate" + e.getLocalizedMessage()); 80 | } 81 | } 82 | return c.getString(R.string.cannotparsecert); 83 | } 84 | 85 | public static String getCertificateValidityString(X509Certificate cert, Resources res) { 86 | try { 87 | cert.checkValidity(); 88 | } catch (CertificateExpiredException ce) { 89 | return "EXPIRED: "; 90 | } catch (CertificateNotYetValidException cny) { 91 | return "NOT YET VALID: "; 92 | } 93 | Date certNotAfter = cert.getNotAfter(); 94 | Date now = new Date(); 95 | long timeLeft = certNotAfter.getTime() - now.getTime(); // Time left in ms 96 | // More than 72h left, display days 97 | // More than 3 months display months 98 | if (timeLeft > 90l * 24 * 3600 * 1000) { 99 | long months = getMonthsDifference(now, certNotAfter); 100 | return res.getString(R.string.months_left, months); 101 | } else if (timeLeft > 72 * 3600 * 1000) { 102 | long days = timeLeft / (24 * 3600 * 1000); 103 | return res.getString(R.string.days_left, days); 104 | } else { 105 | long hours = timeLeft / (3600 * 1000); 106 | return res.getString(R.string.hours_left, hours); 107 | } 108 | } 109 | 110 | public static int getMonthsDifference(Date date1, Date date2) { 111 | int m1 = date1.getYear() * 12 + date1.getMonth(); 112 | int m2 = date2.getYear() * 12 + date2.getMonth(); 113 | return m2 - m1 + 1; 114 | } 115 | 116 | public static String getCertificateFriendlyName(X509Certificate cert) { 117 | X500Principal principal = cert.getSubjectX500Principal(); 118 | byte[] encodedSubject = principal.getEncoded(); 119 | String friendlyName = null; 120 | /* Hack so we do not have to ship a whole Spongy/bouncycastle */ 121 | Exception exp = null; 122 | try { 123 | Class X509NameClass = Class.forName("com.android.org.bouncycastle.asn1.x509.X509Name"); 124 | Method getInstance = X509NameClass.getMethod("getInstance", Object.class); 125 | Hashtable defaultSymbols = (Hashtable) X509NameClass.getField("DefaultSymbols").get(X509NameClass); 126 | if (!defaultSymbols.containsKey("1.2.840.113549.1.9.1")) defaultSymbols.put("1.2.840.113549.1.9.1", "eMail"); 127 | Object subjectName = getInstance.invoke(X509NameClass, encodedSubject); 128 | Method toString = X509NameClass.getMethod("toString", boolean.class, Hashtable.class); 129 | friendlyName = (String) toString.invoke(subjectName, true, defaultSymbols); 130 | } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException | NoSuchFieldException e) { 131 | exp = e; 132 | } 133 | if (exp != null) VpnStatus.logException("Getting X509 Name from certificate", exp); 134 | /* Fallback if the reflection method did not work */ 135 | if (friendlyName == null) friendlyName = principal.getName(); 136 | // Really evil hack to decode email address 137 | // See: http://code.google.com/p/android/issues/detail?id=21531 138 | String[] parts = friendlyName.split(","); 139 | for (int i = 0; i < parts.length; i++) { 140 | String part = parts[i]; 141 | if (part.startsWith("1.2.840.113549.1.9.1=#16")) { 142 | parts[i] = "email=" + ia5decode(part.replace("1.2.840.113549.1.9.1=#16", "")); 143 | } 144 | } 145 | friendlyName = TextUtils.join(",", parts); 146 | return friendlyName; 147 | } 148 | 149 | private static boolean isPrintableChar(char c) { 150 | Character.UnicodeBlock block = Character.UnicodeBlock.of(c); 151 | return (!Character.isISOControl(c)) && block != null && block != Character.UnicodeBlock.SPECIALS; 152 | } 153 | 154 | private static String ia5decode(String ia5string) { 155 | String d = ""; 156 | for (int i = 1; i < ia5string.length(); i = i + 2) { 157 | String hexstr = ia5string.substring(i - 1, i + 1); 158 | char c = (char) Integer.parseInt(hexstr, 16); 159 | if (isPrintableChar(c)) { 160 | d += c; 161 | } else if (i == 1 && (c == 0x12 || c == 0x1b)) { 162 | ; // ignore 163 | } else { 164 | d += "\\x" + hexstr; 165 | } 166 | } 167 | return d; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /vpn/src/main/java/org/spongycastle/util/encoders/Base64.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | 6 | package org.spongycastle.util.encoders; 7 | 8 | import java.io.ByteArrayOutputStream; 9 | import java.io.IOException; 10 | import java.io.OutputStream; 11 | 12 | public class Base64 { 13 | private static final Encoder encoder = new Base64Encoder(); 14 | 15 | /** 16 | * encode the input data producing a base 64 encoded byte array. 17 | * 18 | * @return a byte array containing the base 64 encoded data. 19 | */ 20 | public static byte[] encode(byte[] data) { 21 | int len = (data.length + 2) / 3 * 4; 22 | ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); 23 | 24 | try { 25 | encoder.encode(data, 0, data.length, bOut); 26 | } catch (IOException e) { 27 | throw new RuntimeException("exception encoding base64 string: " + e); 28 | } 29 | 30 | return bOut.toByteArray(); 31 | } 32 | 33 | /** 34 | * Encode the byte data to base 64 writing it to the given output stream. 35 | * 36 | * @return the number of bytes produced. 37 | */ 38 | public static int encode(byte[] data, OutputStream out) throws IOException { 39 | return encoder.encode(data, 0, data.length, out); 40 | } 41 | 42 | /** 43 | * Encode the byte data to base 64 writing it to the given output stream. 44 | * 45 | * @return the number of bytes produced. 46 | */ 47 | public static int encode(byte[] data, int off, int length, OutputStream out) throws IOException { 48 | return encoder.encode(data, off, length, out); 49 | } 50 | 51 | /** 52 | * decode the base 64 encoded input data. It is assumed the input data is valid. 53 | * 54 | * @return a byte array representing the decoded data. 55 | */ 56 | public static byte[] decode(byte[] data) { 57 | int len = data.length / 4 * 3; 58 | ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); 59 | 60 | try { 61 | encoder.decode(data, 0, data.length, bOut); 62 | } catch (IOException e) { 63 | throw new RuntimeException("exception decoding base64 string: " + e); 64 | } 65 | 66 | return bOut.toByteArray(); 67 | } 68 | 69 | /** 70 | * decode the base 64 encoded String data - whitespace will be ignored. 71 | * 72 | * @return a byte array representing the decoded data. 73 | */ 74 | public static byte[] decode(String data) { 75 | int len = data.length() / 4 * 3; 76 | ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); 77 | 78 | try { 79 | encoder.decode(data, bOut); 80 | } catch (IOException e) { 81 | throw new RuntimeException("exception decoding base64 string: " + e); 82 | } 83 | 84 | return bOut.toByteArray(); 85 | } 86 | 87 | /** 88 | * decode the base 64 encoded String data writing it to the given output stream, 89 | * whitespace characters will be ignored. 90 | * 91 | * @return the number of bytes produced. 92 | */ 93 | public static int decode(String data, OutputStream out) throws IOException { 94 | return encoder.decode(data, out); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /vpn/src/main/java/org/spongycastle/util/encoders/Base64Encoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | 6 | package org.spongycastle.util.encoders; 7 | 8 | import java.io.IOException; 9 | import java.io.OutputStream; 10 | 11 | public class Base64Encoder implements Encoder { 12 | protected final byte[] encodingTable = {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/'}; 13 | 14 | protected byte padding = (byte) '='; 15 | 16 | /* 17 | * set up the decoding table. 18 | */ 19 | protected final byte[] decodingTable = new byte[128]; 20 | 21 | protected void initialiseDecodingTable() { 22 | for (int i = 0; i < encodingTable.length; i++) { 23 | decodingTable[encodingTable[i]] = (byte) i; 24 | } 25 | } 26 | 27 | public Base64Encoder() { 28 | initialiseDecodingTable(); 29 | } 30 | 31 | /** 32 | * encode the input data producing a base 64 output stream. 33 | * 34 | * @return the number of bytes produced. 35 | */ 36 | public int encode(byte[] data, int off, int length, OutputStream out) throws IOException { 37 | int modulus = length % 3; 38 | int dataLength = (length - modulus); 39 | int a1, a2, a3; 40 | 41 | for (int i = off; i < off + dataLength; i += 3) { 42 | a1 = data[i] & 0xff; 43 | a2 = data[i + 1] & 0xff; 44 | a3 = data[i + 2] & 0xff; 45 | 46 | out.write(encodingTable[(a1 >>> 2) & 0x3f]); 47 | out.write(encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]); 48 | out.write(encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]); 49 | out.write(encodingTable[a3 & 0x3f]); 50 | } 51 | 52 | /* 53 | * process the tail end. 54 | */ 55 | int b1, b2, b3; 56 | int d1, d2; 57 | 58 | switch (modulus) { 59 | case 0: /* nothing left to do */ 60 | break; 61 | case 1: 62 | d1 = data[off + dataLength] & 0xff; 63 | b1 = (d1 >>> 2) & 0x3f; 64 | b2 = (d1 << 4) & 0x3f; 65 | 66 | out.write(encodingTable[b1]); 67 | out.write(encodingTable[b2]); 68 | out.write(padding); 69 | out.write(padding); 70 | break; 71 | case 2: 72 | d1 = data[off + dataLength] & 0xff; 73 | d2 = data[off + dataLength + 1] & 0xff; 74 | 75 | b1 = (d1 >>> 2) & 0x3f; 76 | b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f; 77 | b3 = (d2 << 2) & 0x3f; 78 | 79 | out.write(encodingTable[b1]); 80 | out.write(encodingTable[b2]); 81 | out.write(encodingTable[b3]); 82 | out.write(padding); 83 | break; 84 | } 85 | 86 | return (dataLength / 3) * 4 + ((modulus == 0) ? 0 : 4); 87 | } 88 | 89 | private boolean ignore(char c) { 90 | return (c == '\n' || c == '\r' || c == '\t' || c == ' '); 91 | } 92 | 93 | /** 94 | * decode the base 64 encoded byte data writing it to the given output stream, 95 | * whitespace characters will be ignored. 96 | * 97 | * @return the number of bytes produced. 98 | */ 99 | public int decode(byte[] data, int off, int length, OutputStream out) throws IOException { 100 | byte b1, b2, b3, b4; 101 | int outLen = 0; 102 | 103 | int end = off + length; 104 | 105 | while (end > off) { 106 | if (!ignore((char) data[end - 1])) { 107 | break; 108 | } 109 | 110 | end--; 111 | } 112 | 113 | int i = off; 114 | int finish = end - 4; 115 | 116 | i = nextI(data, i, finish); 117 | 118 | while (i < finish) { 119 | b1 = decodingTable[data[i++]]; 120 | 121 | i = nextI(data, i, finish); 122 | 123 | b2 = decodingTable[data[i++]]; 124 | 125 | i = nextI(data, i, finish); 126 | 127 | b3 = decodingTable[data[i++]]; 128 | 129 | i = nextI(data, i, finish); 130 | 131 | b4 = decodingTable[data[i++]]; 132 | 133 | out.write((b1 << 2) | (b2 >> 4)); 134 | out.write((b2 << 4) | (b3 >> 2)); 135 | out.write((b3 << 6) | b4); 136 | 137 | outLen += 3; 138 | 139 | i = nextI(data, i, finish); 140 | } 141 | 142 | outLen += decodeLastBlock(out, (char) data[end - 4], (char) data[end - 3], (char) data[end - 2], (char) data[end - 1]); 143 | 144 | return outLen; 145 | } 146 | 147 | private int nextI(byte[] data, int i, int finish) { 148 | while ((i < finish) && ignore((char) data[i])) { 149 | i++; 150 | } 151 | return i; 152 | } 153 | 154 | /** 155 | * decode the base 64 encoded String data writing it to the given output stream, 156 | * whitespace characters will be ignored. 157 | * 158 | * @return the number of bytes produced. 159 | */ 160 | public int decode(String data, OutputStream out) throws IOException { 161 | byte b1, b2, b3, b4; 162 | int length = 0; 163 | 164 | int end = data.length(); 165 | 166 | while (end > 0) { 167 | if (!ignore(data.charAt(end - 1))) { 168 | break; 169 | } 170 | 171 | end--; 172 | } 173 | 174 | int i = 0; 175 | int finish = end - 4; 176 | 177 | i = nextI(data, i, finish); 178 | 179 | while (i < finish) { 180 | b1 = decodingTable[data.charAt(i++)]; 181 | 182 | i = nextI(data, i, finish); 183 | 184 | b2 = decodingTable[data.charAt(i++)]; 185 | 186 | i = nextI(data, i, finish); 187 | 188 | b3 = decodingTable[data.charAt(i++)]; 189 | 190 | i = nextI(data, i, finish); 191 | 192 | b4 = decodingTable[data.charAt(i++)]; 193 | 194 | out.write((b1 << 2) | (b2 >> 4)); 195 | out.write((b2 << 4) | (b3 >> 2)); 196 | out.write((b3 << 6) | b4); 197 | 198 | length += 3; 199 | 200 | i = nextI(data, i, finish); 201 | } 202 | 203 | length += decodeLastBlock(out, data.charAt(end - 4), data.charAt(end - 3), data.charAt(end - 2), data.charAt(end - 1)); 204 | 205 | return length; 206 | } 207 | 208 | private int decodeLastBlock(OutputStream out, char c1, char c2, char c3, char c4) throws IOException { 209 | byte b1, b2, b3, b4; 210 | 211 | if (c3 == padding) { 212 | b1 = decodingTable[c1]; 213 | b2 = decodingTable[c2]; 214 | 215 | out.write((b1 << 2) | (b2 >> 4)); 216 | 217 | return 1; 218 | } else if (c4 == padding) { 219 | b1 = decodingTable[c1]; 220 | b2 = decodingTable[c2]; 221 | b3 = decodingTable[c3]; 222 | 223 | out.write((b1 << 2) | (b2 >> 4)); 224 | out.write((b2 << 4) | (b3 >> 2)); 225 | 226 | return 2; 227 | } else { 228 | b1 = decodingTable[c1]; 229 | b2 = decodingTable[c2]; 230 | b3 = decodingTable[c3]; 231 | b4 = decodingTable[c4]; 232 | 233 | out.write((b1 << 2) | (b2 >> 4)); 234 | out.write((b2 << 4) | (b3 >> 2)); 235 | out.write((b3 << 6) | b4); 236 | 237 | return 3; 238 | } 239 | } 240 | 241 | private int nextI(String data, int i, int finish) { 242 | while ((i < finish) && ignore(data.charAt(i))) { 243 | i++; 244 | } 245 | return i; 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /vpn/src/main/java/org/spongycastle/util/encoders/Encoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | 6 | package org.spongycastle.util.encoders; 7 | 8 | import java.io.IOException; 9 | import java.io.OutputStream; 10 | 11 | /** 12 | * Encode and decode byte arrays (typically from binary to 7-bit ASCII 13 | * encodings). 14 | */ 15 | public interface Encoder 16 | { 17 | int encode(byte[] data, int off, int length, OutputStream out) throws IOException; 18 | 19 | int decode(byte[] data, int off, int length, OutputStream out) throws IOException; 20 | 21 | int decode(String data, OutputStream out) throws IOException; 22 | } 23 | -------------------------------------------------------------------------------- /vpn/src/main/java/org/spongycastle/util/io/pem/PemGenerationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | 6 | package org.spongycastle.util.io.pem; 7 | 8 | import java.io.IOException; 9 | 10 | @SuppressWarnings("serial") 11 | public class PemGenerationException 12 | extends IOException 13 | { 14 | private Throwable cause; 15 | 16 | public PemGenerationException(String message, Throwable cause) 17 | { 18 | super(message); 19 | this.cause = cause; 20 | } 21 | 22 | public PemGenerationException(String message) 23 | { 24 | super(message); 25 | } 26 | 27 | public Throwable getCause() 28 | { 29 | return cause; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /vpn/src/main/java/org/spongycastle/util/io/pem/PemHeader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | 6 | package org.spongycastle.util.io.pem; 7 | 8 | public class PemHeader 9 | { 10 | private String name; 11 | private String value; 12 | 13 | public PemHeader(String name, String value) 14 | { 15 | this.name = name; 16 | this.value = value; 17 | } 18 | 19 | public String getName() 20 | { 21 | return name; 22 | } 23 | 24 | public String getValue() 25 | { 26 | return value; 27 | } 28 | 29 | public int hashCode() 30 | { 31 | return getHashCode(this.name) + 31 * getHashCode(this.value); 32 | } 33 | 34 | public boolean equals(Object o) 35 | { 36 | if (!(o instanceof PemHeader)) 37 | { 38 | return false; 39 | } 40 | 41 | PemHeader other = (PemHeader)o; 42 | 43 | return other == this || (isEqual(this.name, other.name) && isEqual(this.value, other.value)); 44 | } 45 | 46 | private int getHashCode(String s) 47 | { 48 | if (s == null) 49 | { 50 | return 1; 51 | } 52 | 53 | return s.hashCode(); 54 | } 55 | 56 | private boolean isEqual(String s1, String s2) 57 | { 58 | if (s1 == s2) 59 | { 60 | return true; 61 | } 62 | 63 | if (s1 == null || s2 == null) 64 | { 65 | return false; 66 | } 67 | 68 | return s1.equals(s2); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /vpn/src/main/java/org/spongycastle/util/io/pem/PemObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | 6 | package org.spongycastle.util.io.pem; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | @SuppressWarnings("all") 13 | public class PemObject 14 | implements PemObjectGenerator 15 | { 16 | private static final List EMPTY_LIST = Collections.unmodifiableList(new ArrayList()); 17 | 18 | private String type; 19 | private List headers; 20 | private byte[] content; 21 | 22 | /** 23 | * Generic constructor for object without headers. 24 | * 25 | * @param type pem object type. 26 | * @param content the binary content of the object. 27 | */ 28 | public PemObject(String type, byte[] content) 29 | { 30 | this(type, EMPTY_LIST, content); 31 | } 32 | 33 | /** 34 | * Generic constructor for object with headers. 35 | * 36 | * @param type pem object type. 37 | * @param headers a list of PemHeader objects. 38 | * @param content the binary content of the object. 39 | */ 40 | public PemObject(String type, List headers, byte[] content) 41 | { 42 | this.type = type; 43 | this.headers = Collections.unmodifiableList(headers); 44 | this.content = content; 45 | } 46 | 47 | public String getType() 48 | { 49 | return type; 50 | } 51 | 52 | public List getHeaders() 53 | { 54 | return headers; 55 | } 56 | 57 | public byte[] getContent() 58 | { 59 | return content; 60 | } 61 | 62 | public PemObject generate() 63 | throws PemGenerationException 64 | { 65 | return this; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /vpn/src/main/java/org/spongycastle/util/io/pem/PemObjectGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | 6 | package org.spongycastle.util.io.pem; 7 | 8 | public interface PemObjectGenerator 9 | { 10 | PemObject generate() 11 | throws PemGenerationException; 12 | } 13 | -------------------------------------------------------------------------------- /vpn/src/main/java/org/spongycastle/util/io/pem/PemReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | 6 | package org.spongycastle.util.io.pem; 7 | 8 | import java.io.BufferedReader; 9 | import java.io.IOException; 10 | import java.io.Reader; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import org.spongycastle.util.encoders.Base64; 15 | 16 | public class PemReader extends BufferedReader { 17 | private static final String BEGIN = "-----BEGIN "; 18 | private static final String END = "-----END "; 19 | 20 | public PemReader(Reader reader) { 21 | super(reader); 22 | } 23 | 24 | public PemObject readPemObject() throws IOException { 25 | String line = readLine(); 26 | 27 | while (line != null && !line.startsWith(BEGIN)) { 28 | line = readLine(); 29 | } 30 | 31 | if (line != null) { 32 | line = line.substring(BEGIN.length()); 33 | int index = line.indexOf('-'); 34 | String type = line.substring(0, index); 35 | 36 | if (index > 0) { 37 | return loadObject(type); 38 | } 39 | } 40 | 41 | return null; 42 | } 43 | 44 | private PemObject loadObject(String type) throws IOException { 45 | String line; 46 | String endMarker = END + type; 47 | StringBuilder buf = new StringBuilder(); 48 | List headers = new ArrayList(); 49 | 50 | while ((line = readLine()) != null) { 51 | if (line.indexOf(":") >= 0) { 52 | int index = line.indexOf(':'); 53 | String hdr = line.substring(0, index); 54 | String value = line.substring(index + 1).trim(); 55 | 56 | headers.add(new PemHeader(hdr, value)); 57 | 58 | continue; 59 | } 60 | 61 | if (line.indexOf(endMarker) != -1) { 62 | break; 63 | } 64 | 65 | buf.append(line.trim()); 66 | } 67 | 68 | if (line == null) { 69 | throw new IOException(endMarker + " not found"); 70 | } 71 | 72 | return new PemObject(type, headers, Base64.decode(buf.toString())); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /vpn/src/main/java/org/spongycastle/util/io/pem/PemWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016 Arne Schwabe 3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt 4 | */ 5 | 6 | package org.spongycastle.util.io.pem; 7 | 8 | import java.io.BufferedWriter; 9 | import java.io.IOException; 10 | import java.io.Writer; 11 | import java.util.Iterator; 12 | 13 | import org.spongycastle.util.encoders.Base64; 14 | 15 | /** 16 | * A generic PEM writer, based on RFC 1421 17 | */ 18 | @SuppressWarnings("all") 19 | public class PemWriter 20 | extends BufferedWriter 21 | { 22 | private static final int LINE_LENGTH = 64; 23 | 24 | private final int nlLength; 25 | private char[] buf = new char[LINE_LENGTH]; 26 | 27 | /** 28 | * Base constructor. 29 | * 30 | * @param out output stream to use. 31 | */ 32 | public PemWriter(Writer out) 33 | { 34 | super(out); 35 | 36 | String nl = System.getProperty("line.separator"); 37 | if (nl != null) 38 | { 39 | nlLength = nl.length(); 40 | } 41 | else 42 | { 43 | nlLength = 2; 44 | } 45 | } 46 | 47 | /** 48 | * Return the number of bytes or characters required to contain the 49 | * passed in object if it is PEM encoded. 50 | * 51 | * @param obj pem object to be output 52 | * @return an estimate of the number of bytes 53 | */ 54 | public int getOutputSize(PemObject obj) 55 | { 56 | // BEGIN and END boundaries. 57 | int size = (2 * (obj.getType().length() + 10 + nlLength)) + 6 + 4; 58 | 59 | if (!obj.getHeaders().isEmpty()) 60 | { 61 | for (Iterator it = obj.getHeaders().iterator(); it.hasNext();) 62 | { 63 | PemHeader hdr = (PemHeader)it.next(); 64 | 65 | size += hdr.getName().length() + ": ".length() + hdr.getValue().length() + nlLength; 66 | } 67 | 68 | size += nlLength; 69 | } 70 | 71 | // base64 encoding 72 | int dataLen = ((obj.getContent().length + 2) / 3) * 4; 73 | 74 | size += dataLen + (((dataLen + LINE_LENGTH - 1) / LINE_LENGTH) * nlLength); 75 | 76 | return size; 77 | } 78 | 79 | public void writeObject(PemObjectGenerator objGen) 80 | throws IOException 81 | { 82 | PemObject obj = objGen.generate(); 83 | 84 | writePreEncapsulationBoundary(obj.getType()); 85 | 86 | if (!obj.getHeaders().isEmpty()) 87 | { 88 | for (Iterator it = obj.getHeaders().iterator(); it.hasNext();) 89 | { 90 | PemHeader hdr = (PemHeader)it.next(); 91 | 92 | this.write(hdr.getName()); 93 | this.write(": "); 94 | this.write(hdr.getValue()); 95 | this.newLine(); 96 | } 97 | 98 | this.newLine(); 99 | } 100 | 101 | writeEncoded(obj.getContent()); 102 | writePostEncapsulationBoundary(obj.getType()); 103 | } 104 | 105 | private void writeEncoded(byte[] bytes) 106 | throws IOException 107 | { 108 | bytes = Base64.encode(bytes); 109 | 110 | for (int i = 0; i < bytes.length; i += buf.length) 111 | { 112 | int index = 0; 113 | 114 | while (index != buf.length) 115 | { 116 | if ((i + index) >= bytes.length) 117 | { 118 | break; 119 | } 120 | buf[index] = (char)bytes[i + index]; 121 | index++; 122 | } 123 | this.write(buf, 0, index); 124 | this.newLine(); 125 | } 126 | } 127 | 128 | private void writePreEncapsulationBoundary( 129 | String type) 130 | throws IOException 131 | { 132 | this.write("-----BEGIN " + type + "-----"); 133 | this.newLine(); 134 | } 135 | 136 | private void writePostEncapsulationBoundary( 137 | String type) 138 | throws IOException 139 | { 140 | this.write("-----END " + type + "-----"); 141 | this.newLine(); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /vpn/src/main/jniLibs/arm64-v8a/libjbcrypto.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/arm64-v8a/libjbcrypto.so -------------------------------------------------------------------------------- /vpn/src/main/jniLibs/arm64-v8a/libopenvpn.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/arm64-v8a/libopenvpn.so -------------------------------------------------------------------------------- /vpn/src/main/jniLibs/arm64-v8a/libopvpnutil.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/arm64-v8a/libopvpnutil.so -------------------------------------------------------------------------------- /vpn/src/main/jniLibs/armeabi-v7a/libjbcrypto.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/armeabi-v7a/libjbcrypto.so -------------------------------------------------------------------------------- /vpn/src/main/jniLibs/armeabi-v7a/libopenvpn.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/armeabi-v7a/libopenvpn.so -------------------------------------------------------------------------------- /vpn/src/main/jniLibs/armeabi-v7a/libopvpnutil.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/armeabi-v7a/libopvpnutil.so -------------------------------------------------------------------------------- /vpn/src/main/jniLibs/armeabi/libjbcrypto.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/armeabi/libjbcrypto.so -------------------------------------------------------------------------------- /vpn/src/main/jniLibs/armeabi/libopenvpn.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/armeabi/libopenvpn.so -------------------------------------------------------------------------------- /vpn/src/main/jniLibs/armeabi/libopvpnutil.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/armeabi/libopvpnutil.so -------------------------------------------------------------------------------- /vpn/src/main/jniLibs/mips/libjbcrypto.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/mips/libjbcrypto.so -------------------------------------------------------------------------------- /vpn/src/main/jniLibs/mips/libopenvpn.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/mips/libopenvpn.so -------------------------------------------------------------------------------- /vpn/src/main/jniLibs/mips/libopvpnutil.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/mips/libopvpnutil.so -------------------------------------------------------------------------------- /vpn/src/main/jniLibs/x86/libjbcrypto.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/x86/libjbcrypto.so -------------------------------------------------------------------------------- /vpn/src/main/jniLibs/x86/libopenvpn.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/x86/libopenvpn.so -------------------------------------------------------------------------------- /vpn/src/main/jniLibs/x86/libopvpnutil.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/x86/libopvpnutil.so -------------------------------------------------------------------------------- /vpn/src/main/jniLibs/x86_64/libjbcrypto.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/x86_64/libjbcrypto.so -------------------------------------------------------------------------------- /vpn/src/main/jniLibs/x86_64/libopenvpn.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/x86_64/libopenvpn.so -------------------------------------------------------------------------------- /vpn/src/main/jniLibs/x86_64/libopvpnutil.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/jniLibs/x86_64/libopvpnutil.so -------------------------------------------------------------------------------- /vpn/src/main/res/drawable-hdpi/ic_menu_archive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/drawable-hdpi/ic_menu_archive.png -------------------------------------------------------------------------------- /vpn/src/main/res/drawable-hdpi/ic_menu_copy_holo_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/drawable-hdpi/ic_menu_copy_holo_light.png -------------------------------------------------------------------------------- /vpn/src/main/res/drawable-hdpi/ic_menu_log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/drawable-hdpi/ic_menu_log.png -------------------------------------------------------------------------------- /vpn/src/main/res/drawable-hdpi/ic_stat_vpn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/drawable-hdpi/ic_stat_vpn.png -------------------------------------------------------------------------------- /vpn/src/main/res/drawable-hdpi/ic_stat_vpn_empty_halo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/drawable-hdpi/ic_stat_vpn_empty_halo.png -------------------------------------------------------------------------------- /vpn/src/main/res/drawable-hdpi/ic_stat_vpn_offline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/drawable-hdpi/ic_stat_vpn_offline.png -------------------------------------------------------------------------------- /vpn/src/main/res/drawable-hdpi/ic_stat_vpn_outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/drawable-hdpi/ic_stat_vpn_outline.png -------------------------------------------------------------------------------- /vpn/src/main/res/drawable-hdpi/vpn_item_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/drawable-hdpi/vpn_item_settings.png -------------------------------------------------------------------------------- /vpn/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /vpn/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /vpn/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /vpn/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /vpn/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /vpn/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuger/OpenVPN-Android/281c6fa484734c4e246e682a2ed93ed0941e3e93/vpn/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /vpn/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /vpn/src/main/res/values/refs.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | @android:drawable/ic_menu_close_clear_cancel 9 | @android:drawable/ic_media_play 10 | @android:drawable/ic_media_pause 11 | -------------------------------------------------------------------------------- /vpn/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 18 | -------------------------------------------------------------------------------- /vpn/src/test/java/de/blinkt/openvpn/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package de.blinkt.openvpn; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } --------------------------------------------------------------------------------