Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
66 changes: 66 additions & 0 deletions cmd/skywirevisormobile/android/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Skywire VPN draft (Android)

## Prerequisites
This project uses Skywire mobile library (`skywiremob`) to use all the needed Skywire infrastructure. In order to build
and use this library, one needs to install `gomobile` (https://github.com/golang/mobile).

## Building and Running
To build the library you need to use `gomobile`. Main `Makefile` already contains target `build-android` which
can be used.
IMPORTANT: regardless of go modules and other great stuff done by the Go team, in order to
use `gomobile` you need to put Skywire code according to `GOPATH`. Otherwise you'll get all kinds of errors.
The output file (`.aar` for Android) may be used straight from the mobile app code.

## Skywire Mobile API
- `PrintString(string)`: Logs string argument using info log level. May be useful to use it instead of standard logging features of mobile apps for debugging.
All the output strings are prefixed with `GoLog`, so printing logs with this func one may grep all the logs both from `skywiremob`
internal and mobile application;
- `IsPKValid(string) string`: Checks if passed pub key is valid. Returns non-empty string with error in case of failure;
- `GetMTU() int`: Gets VPN connection MTU;
- `GetTUNIPPrefix() int`: Gets netmask prefix of TUN IP address;
- `IsVPNReady() bool`: Checks whether VPN client is ready on the Go side. Once it is, the mobile application is free to start
forwarding packets. Starts returning `true` after the `ServerVPN` call;
- `PrepareVisor() string`: Creates and runs visor instance. Returns non-empty string with error in case of failure;
- `NextDmsgSocket() int`: Returns file descriptor of the Dmsg socket. There may be more than one socket in use by the dmsg client, so
this function should be called repeatedly until next call returns 0.
- `PrepareVPNClient(string, string) string`: Creates VPN client instance. First string argument is remote VPN server pub key, second one is passcode to
authenticate within the server. Returns non-empty string with error in case of failure;
- `ShakeHands() string`: Requires `PrepareVPNClient` to be called first. Performs handshake between the client and the server.
Returns non-empty string with error in case of failure;
- `TUNIP() string`: Requires `ShakeHands` to be called first. Returns the assigned TUN IP;
- `TUNGateway() string`: Requires `ShakeHands` to be called first. Returns the assigned TUN gateway;
- `StopVisor() string`: Stops currently running visor. Returns non-empty string with error in case of failure;
- `SetMobileAppAddr(string)`: Passes address of the UDP connection opened on the mobile application side;
- `ServeVPN()`: Starts off the goroutine serving VPN connection. After this call `IsVPNReady` starts returning `true`;
- `StartListeningUDP() string`: Opens UDP listener on the Go side. Returns non-empty string with error in case of failure;
- `IsVisorStarting() bool`: Checks if visor is starting. Will get `false` when it's fully functional;
- `IsVisorRunning() bool`: Checks if visor is running. Will get `true` whn visor is fully functional;
- `WaitVisorReady() string`: Blocks until visor gets fully initialized. Returns non-empty error string in case of failure;
- `StopVPNClient`: Stops VPN client without stopping visor itself;
- `StopListeningUDP`: Closes UDP socket;
- `VPNBandwidthSent`: Returns amount of bandwidth sent over VPN (bytes);
- `VPNBandwidthReceived`: Returns amount of bandwidth received over VPN (bytes);
- `VPNLatency`: Returns latency (ms);
- `VPNThroughput`: Returns throughput (bytes/s).


## Mobile/Go Communication
API may seem a bit complicated at first. Currently tested for Android devices, should be used with caution on iOS.
Mobile app communicates with the Go part via UDP. All the packets are sent to the Go part via UDP and then get resent
to the Skywire network.

To setup the Go side properly you need to call at least:
- `PrepareVisor` to run the visor;
- `PrepareVPNClient` to run the VPN client;
- `ShakeHands` to perform handshake with the server;
- `StartListeningUDP` to open the UDP listener on the Go side;
- `ServeVPN` to start forwarding traffic.

All other calls should be done as needed.

### Android
Consult this page: https://developer.android.com/guide/topics/connectivity/vpn

In the example mobile app communicates with the remote server via `DatagramChannel`. Socket opened to the server gets protected
with the `protect` method. We do the same here. But instead of a remote server we open the `DatagramChannel` to the Go part of the app.
We protect not only the tunnel socket, but also we need to protect all the sockets used for `Dmsg` communication to let traffic go back and forth freely.
61 changes: 61 additions & 0 deletions cmd/skywirevisormobile/android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2015 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
apply plugin: 'com.android.application'

repositories {
flatDir {
dirs '.'
}
maven { url 'https://jitpack.io' }
}

android {
compileSdkVersion 29

defaultConfig {
applicationId "com.skywire.skycoin.vpn"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}


dependencies {
// Appcompat.
implementation "androidx.appcompat:appcompat:1.2.0"
implementation 'com.google.android.material:material:1.2.1'
implementation "androidx.preference:preference:1.1.1"
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.viewpager2:viewpager2:1.0.0"

// Skywire lib.
implementation(name:'skywire', ext:'aar')

// RxJava.
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
implementation 'io.reactivex.rxjava3:rxjava:3.0.0'

// Retrofit.
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'

// MPAndroidChart.
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
}
54 changes: 54 additions & 0 deletions cmd/skywirevisormobile/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.skywire.skycoin.vpn" >

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

<application
android:name=".App"
android:allowBackup="true"
android:label="@string/general_app_name"
android:icon="@mipmap/ic_launcher"
android:theme="@style/AppTheme"
>

<receiver
android:directBootAware="true"
android:name=".Receiver"
android:enabled="true">
<intent-filter android:priority="2000000000">
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>

<service android:name=".vpn.SkywireVPNService"
android:description="@string/general_service_description"
android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
</service>

<activity
android:name=".activities.index.IndexActivity"
android:configChanges="keyboardHidden"
android:launchMode="singleTask"
android:label="@string/general_app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity
android:name=".activities.apps.AppsActivity"
android:configChanges="keyboardHidden"
android:label="@string/tmp_select_apps_title" >
</activity>

</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package com.skywire.skycoin.vpn;

import android.app.Activity;
import android.app.Application;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.skywire.skycoin.vpn.helpers.HelperFunctions;
import com.skywire.skycoin.vpn.helpers.Notifications;
import com.skywire.skycoin.vpn.vpn.VPNCoordinator;

import io.reactivex.rxjava3.plugins.RxJavaPlugins;

/**
* Class for the main app instance.
*/
public class App extends Application {
/**
* Class used internally to know when there are activities being displayed.
*/
private static class ActivityLifecycleCallback implements Application.ActivityLifecycleCallbacks {

// How many activities are being shown.
private static int foregroundActivities = 0;

// Functions for knowing when activities start and stop being shown.
@Override
public void onActivityResumed(@NonNull final Activity activity) { foregroundActivities++; }
@Override
public void onActivityStopped(@NonNull final Activity activity) { foregroundActivities--; }

/**
* Returns if there is at least one activity being displayed.
*/
public static boolean isApplicationInForeground() { return foregroundActivities > 0; }

// Other functions needed by the interface.
@Override
public void onActivityPaused(@NonNull Activity activity) { }
@Override
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) { }
@Override
public void onActivityDestroyed(@NonNull Activity activity) { }
@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { }
@Override
public void onActivityStarted(@NonNull Activity activity) { }
}

/**
* Reference to the current app instance.
*/
private static Context appContext;

@Override
public void onCreate() {
super.onCreate();
// Save the current app instance.
appContext = this;

// Ensure the singleton is initialized early.
VPNCoordinator.getInstance();

// Create the notification channels, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Channel for the VPN service state updates.
NotificationChannel stateChannel = new NotificationChannel(
Notifications.NOTIFICATION_CHANNEL_ID,
getString(R.string.general_app_name),
NotificationManager.IMPORTANCE_DEFAULT
);
stateChannel.setDescription(getString(R.string.general_notification_channel_description));
stateChannel.setSound(null,null);
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(stateChannel);

// Channel for alerts.
NotificationChannel alertsChannel = new NotificationChannel(
Notifications.ALERT_NOTIFICATION_CHANNEL_ID,
getString(R.string.general_alert_notification_name),
NotificationManager.IMPORTANCE_HIGH
);
alertsChannel.setDescription(getString(R.string.general_alert_notification_channel_description));
notificationManager.createNotificationChannel(alertsChannel);
}

// Code for precessing errors which were not caught by the normal error management
// procedures RxJava has. This prevents the app to be closed by unexpected errors, mainly
// code trying to report events in closed observables.
RxJavaPlugins.setErrorHandler(throwable -> {
HelperFunctions.logError("ERROR INSIDE RX: ", throwable);
});

// Detect when activities are started and stopped.
registerActivityLifecycleCallbacks(new ActivityLifecycleCallback());
}

/**
* Gets the current app context.
*/
public static Context getContext(){
return appContext;
}

/**
* Gets if the UI is being displayed.
*/
public static boolean displayingUI(){
return ActivityLifecycleCallback.isApplicationInForeground();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.skywire.skycoin.vpn;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

import com.skywire.skycoin.vpn.vpn.VPNCoordinator;
import com.skywire.skycoin.vpn.vpn.VPNGeneralPersistentData;

/**
* Class for receiving the system boot event broadcast.
*/
public class Receiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
// If the option for starting the service automatically after booting the OS is active
// and the service is not currently running, start the service.
if (VPNGeneralPersistentData.getStartOnBoot() && !VPNCoordinator.getInstance().isServiceRunning()) {
VPNCoordinator.getInstance().activateAutostart();
}
}
}
}
Loading