* commit '51cf4f22830e7b4e52278db0772692c6db5f327d': Android VPN sample for ICS SDK
This commit is contained in:
@@ -184,6 +184,7 @@ development/samples/SpinnerTest samples/${PLATFORM_NAME}/SpinnerT
|
||||
development/samples/TicTacToeLib samples/${PLATFORM_NAME}/TicTacToeLib
|
||||
development/samples/TicTacToeMain samples/${PLATFORM_NAME}/TicTacToeMain
|
||||
development/samples/TtsEngine samples/${PLATFORM_NAME}/TtsEngine
|
||||
development/samples/ToyVpn samples/${PLATFORM_NAME}/ToyVpn
|
||||
development/samples/USB/MissileLauncher samples/${PLATFORM_NAME}/USB/MissileLauncher
|
||||
development/samples/USB/AdbTest samples/${PLATFORM_NAME}/USB/AdbTest
|
||||
development/samples/VoiceRecognitionService samples/${PLATFORM_NAME}/VoiceRecognitionService
|
||||
|
||||
16
samples/ToyVpn/Android.mk
Normal file
16
samples/ToyVpn/Android.mk
Normal file
@@ -0,0 +1,16 @@
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE_TAGS := samples
|
||||
|
||||
# Only compile source java files in this apk.
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
|
||||
LOCAL_PACKAGE_NAME := ToyVpn
|
||||
|
||||
LOCAL_SDK_VERSION := current
|
||||
|
||||
include $(BUILD_PACKAGE)
|
||||
|
||||
# Use the following include to make our test apk.
|
||||
include $(call all-makefiles-under,$(LOCAL_PATH))
|
||||
41
samples/ToyVpn/AndroidManifest.xml
Normal file
41
samples/ToyVpn/AndroidManifest.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2011 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.android.toyvpn">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<uses-sdk android:minSdkVersion="14"/>
|
||||
|
||||
<application android:label="@string/app">
|
||||
|
||||
<activity android:name=".ToyVpnClient"
|
||||
android:configChanges="orientation|keyboardHidden">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service android:name=".ToyVpnService"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
7
samples/ToyVpn/_index.html
Executable file
7
samples/ToyVpn/_index.html
Executable file
@@ -0,0 +1,7 @@
|
||||
<p>ToyVPN is a sample application that shows how to build a VPN client using the <a href="../../../reference/android/net/VpnService.html">VpnService</a> class introduced in API level 14.</p>
|
||||
|
||||
<p>This application consists of an Android client and a sample implementation of a server. It performs IP over UDP and is capable of doing seamless handover between different networks as long as it receives the same VPN parameters.</p>
|
||||
|
||||
<p>The sample code of the server-side implementation is Linux-specific and is available in the <code>server</code> directory. To run the server or port it to another platform, please see comments in the code for the details.</p>
|
||||
|
||||
<img alt="" src="../images/vpn-confirmation.png" />
|
||||
37
samples/ToyVpn/res/layout/form.xml
Normal file
37
samples/ToyVpn/res/layout/form.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2011 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="3mm">
|
||||
|
||||
<TextView style="@style/item" android:text="@string/address"/>
|
||||
<EditText style="@style/item" android:id="@+id/address"/>
|
||||
|
||||
<TextView style="@style/item" android:text="@string/port"/>
|
||||
<EditText style="@style/item" android:id="@+id/port"/>
|
||||
|
||||
<TextView style="@style/item" android:text="@string/secret"/>
|
||||
<EditText style="@style/item" android:id="@+id/secret" android:password="true"/>
|
||||
|
||||
<Button style="@style/item" android:id="@+id/connect" android:text="@string/connect"/>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
29
samples/ToyVpn/res/values/strings.xml
Normal file
29
samples/ToyVpn/res/values/strings.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2011 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<string name="app">ToyVPN</string>
|
||||
|
||||
<string name="address">Server Address:</string>
|
||||
<string name="port">Server Port:</string>
|
||||
<string name="secret">Shared Secret:</string>
|
||||
<string name="connect">Connect!</string>
|
||||
|
||||
<string name="connecting">ToyVPN is connecting...</string>
|
||||
<string name="connected">ToyVPN is connected!</string>
|
||||
<string name="disconnected">ToyVPN is disconnected!</string>
|
||||
</resources>
|
||||
25
samples/ToyVpn/res/values/styles.xml
Normal file
25
samples/ToyVpn/res/values/styles.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2011 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<style name="item">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
|
||||
<item name="android:singleLine">true</item>
|
||||
</style>
|
||||
</resources>
|
||||
18
samples/ToyVpn/server/linux/Makefile
Normal file
18
samples/ToyVpn/server/linux/Makefile
Normal file
@@ -0,0 +1,18 @@
|
||||
#
|
||||
# Copyright (C) 2011 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
all:
|
||||
g++ -Wall -o ToyVpnServer ToyVpnServer.cpp
|
||||
288
samples/ToyVpn/server/linux/ToyVpnServer.cpp
Normal file
288
samples/ToyVpn/server/linux/ToyVpnServer.cpp
Normal file
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
// There are several ways to play with this program. Here we just give an
|
||||
// example for the simplest scenario. Let us say that a Linux box has a
|
||||
// public IPv4 address on eth0. Please try the following steps and adjust
|
||||
// the parameters when necessary.
|
||||
//
|
||||
// # Enable IP forwarding
|
||||
// echo 1 > /proc/sys/net/ipv4/ip_forward
|
||||
//
|
||||
// # Pick a range of private addresses and perform NAT over eth0.
|
||||
// iptables -t nat -A POSTROUTING -s 10.0.0.0/8 -o eth0 -j MASQUERADE
|
||||
//
|
||||
// # Create a TUN interface.
|
||||
// ip tuntap add dev tun0 mode tun
|
||||
//
|
||||
// # Set the addresses and bring up the interface.
|
||||
// ifconfig tun0 10.0.0.1 dstaddr 10.0.0.2 up
|
||||
//
|
||||
// # Create a server on port 8000 with shared secret "test".
|
||||
// ./ToyVpnServer tun0 8000 test -m 1400 -a 10.0.0.2 32 -d 8.8.8.8 -r 0.0.0.0 0
|
||||
//
|
||||
// This program only handles a session at a time. To allow multiple sessions,
|
||||
// multiple servers can be created on the same port, but each of them requires
|
||||
// its own TUN interface. A short shell script will be sufficient. Since this
|
||||
// program is designed for demonstration purpose, it performs neither strong
|
||||
// authentication nor encryption. DO NOT USE IT IN PRODUCTION!
|
||||
|
||||
#include <net/if.h>
|
||||
#include <linux/if_tun.h>
|
||||
|
||||
static int get_interface(char *name)
|
||||
{
|
||||
int interface = open("/dev/net/tun", O_RDWR | O_NONBLOCK);
|
||||
|
||||
ifreq ifr;
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
|
||||
strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
|
||||
|
||||
if (ioctl(interface, TUNSETIFF, &ifr)) {
|
||||
perror("Cannot get TUN interface");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return interface;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#error Sorry, you have to implement this part by yourself.
|
||||
|
||||
#endif
|
||||
|
||||
static int get_tunnel(char *port, char *secret)
|
||||
{
|
||||
// We use an IPv6 socket to cover both IPv4 and IPv6.
|
||||
int tunnel = socket(AF_INET6, SOCK_DGRAM, 0);
|
||||
int flag = 1;
|
||||
setsockopt(tunnel, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
|
||||
flag = 0;
|
||||
setsockopt(tunnel, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag));
|
||||
|
||||
// Accept packets received on any local address.
|
||||
sockaddr_in6 addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin6_family = AF_INET6;
|
||||
addr.sin6_port = htons(atoi(port));
|
||||
|
||||
// Call bind(2) in a loop since Linux does not have SO_REUSEPORT.
|
||||
while (bind(tunnel, (sockaddr *)&addr, sizeof(addr))) {
|
||||
if (errno != EADDRINUSE) {
|
||||
return -1;
|
||||
}
|
||||
usleep(100000);
|
||||
}
|
||||
|
||||
// Receive packets till the secret matches.
|
||||
char packet[1024];
|
||||
socklen_t addrlen;
|
||||
do {
|
||||
addrlen = sizeof(addr);
|
||||
int n = recvfrom(tunnel, packet, sizeof(packet), 0,
|
||||
(sockaddr *)&addr, &addrlen);
|
||||
if (n <= 0) {
|
||||
return -1;
|
||||
}
|
||||
packet[n] = 0;
|
||||
} while (packet[0] != 0 || strcmp(secret, &packet[1]));
|
||||
|
||||
// Connect to the client as we only handle one client at a time.
|
||||
connect(tunnel, (sockaddr *)&addr, addrlen);
|
||||
return tunnel;
|
||||
}
|
||||
|
||||
static void build_parameters(char *parameters, int size, int argc, char **argv)
|
||||
{
|
||||
// Well, for simplicity, we just concatenate them (almost) blindly.
|
||||
int offset = 0;
|
||||
for (int i = 4; i < argc; ++i) {
|
||||
char *parameter = argv[i];
|
||||
int length = strlen(parameter);
|
||||
char delimiter = ',';
|
||||
|
||||
// If it looks like an option, prepend a space instead of a comma.
|
||||
if (length == 2 && parameter[0] == '-') {
|
||||
++parameter;
|
||||
--length;
|
||||
delimiter = ' ';
|
||||
}
|
||||
|
||||
// This is just a demo app, really.
|
||||
if (offset + length >= size) {
|
||||
puts("Parameters are too large");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Append the delimiter and the parameter.
|
||||
parameters[offset] = delimiter;
|
||||
memcpy(¶meters[offset + 1], parameter, length);
|
||||
offset += 1 + length;
|
||||
}
|
||||
|
||||
// Fill the rest of the space with spaces.
|
||||
memset(¶meters[offset], ' ', size - offset);
|
||||
|
||||
// Control messages always start with zero.
|
||||
parameters[0] = 0;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 5) {
|
||||
printf("Usage: %s <tunN> <port> <secret> options...\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
" -m <MTU> for the maximum transmission unit\n"
|
||||
" -a <address> <prefix-length> for the private address\n"
|
||||
" -r <address> <prefix-length> for the forwarding route\n"
|
||||
" -d <address> for the domain name server\n"
|
||||
" -s <domain> for the search domain\n"
|
||||
"\n"
|
||||
"Note that TUN interface needs to be configured properly\n"
|
||||
"BEFORE running this program. For more information, please\n"
|
||||
"read the comments in the source code.\n\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Parse the arguments and set the parameters.
|
||||
char parameters[1024];
|
||||
build_parameters(parameters, sizeof(parameters), argc, argv);
|
||||
|
||||
// Get TUN interface.
|
||||
int interface = get_interface(argv[1]);
|
||||
|
||||
// Wait for a tunnel.
|
||||
int tunnel;
|
||||
while ((tunnel = get_tunnel(argv[2], argv[3])) != -1) {
|
||||
printf("%s: Here comes a new tunnel\n", argv[1]);
|
||||
|
||||
// On UN*X, there are many ways to deal with multiple file
|
||||
// descriptors, such as poll(2), select(2), epoll(7) on Linux,
|
||||
// kqueue(2) on FreeBSD, pthread(3), or even fork(2). Here we
|
||||
// mimic everything from the client, so their source code can
|
||||
// be easily compared side by side.
|
||||
|
||||
// Put the tunnel into non-blocking mode.
|
||||
fcntl(tunnel, F_SETFL, O_NONBLOCK);
|
||||
|
||||
// Send the parameters several times in case of packet loss.
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
send(tunnel, parameters, sizeof(parameters), MSG_NOSIGNAL);
|
||||
}
|
||||
|
||||
// Allocate the buffer for a single packet.
|
||||
char packet[32767];
|
||||
|
||||
// We use a timer to determine the status of the tunnel. It
|
||||
// works on both sides. A positive value means sending, and
|
||||
// any other means receiving. We start with receiving.
|
||||
int timer = 0;
|
||||
|
||||
// We keep forwarding packets till something goes wrong.
|
||||
while (true) {
|
||||
// Assume that we did not make any progress in this iteration.
|
||||
bool idle = true;
|
||||
|
||||
// Read the outgoing packet from the input stream.
|
||||
int length = read(interface, packet, sizeof(packet));
|
||||
if (length > 0) {
|
||||
// Write the outgoing packet to the tunnel.
|
||||
send(tunnel, packet, length, MSG_NOSIGNAL);
|
||||
|
||||
// There might be more outgoing packets.
|
||||
idle = false;
|
||||
|
||||
// If we were receiving, switch to sending.
|
||||
if (timer < 1) {
|
||||
timer = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the incoming packet from the tunnel.
|
||||
length = recv(tunnel, packet, sizeof(packet), 0);
|
||||
if (length == 0) {
|
||||
break;
|
||||
}
|
||||
if (length > 0) {
|
||||
// Ignore control messages, which start with zero.
|
||||
if (packet[0] != 0) {
|
||||
// Write the incoming packet to the output stream.
|
||||
write(interface, packet, length);
|
||||
}
|
||||
|
||||
// There might be more incoming packets.
|
||||
idle = false;
|
||||
|
||||
// If we were sending, switch to receiving.
|
||||
if (timer > 0) {
|
||||
timer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// If we are idle or waiting for the network, sleep for a
|
||||
// fraction of time to avoid busy looping.
|
||||
if (idle) {
|
||||
usleep(100000);
|
||||
|
||||
// Increase the timer. This is inaccurate but good enough,
|
||||
// since everything is operated in non-blocking mode.
|
||||
timer += (timer > 0) ? 100 : -100;
|
||||
|
||||
// We are receiving for a long time but not sending.
|
||||
// Can you figure out why we use a different value? :)
|
||||
if (timer < -16000) {
|
||||
// Send empty control messages.
|
||||
packet[0] = 0;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
send(tunnel, packet, 1, MSG_NOSIGNAL);
|
||||
}
|
||||
|
||||
// Switch to sending.
|
||||
timer = 1;
|
||||
}
|
||||
|
||||
// We are sending for a long time but not receiving.
|
||||
if (timer > 20000) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("%s: The tunnel is broken\n", argv[1]);
|
||||
close(tunnel);
|
||||
}
|
||||
perror("Cannot create tunnels");
|
||||
exit(1);
|
||||
}
|
||||
1
samples/ToyVpn/server/linux/_index.html
Executable file
1
samples/ToyVpn/server/linux/_index.html
Executable file
@@ -0,0 +1 @@
|
||||
<p>Server code can be found in the ToyVPN sample distributed in the SDK.</p>
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.toyvpn;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.VpnService;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Button;
|
||||
|
||||
public class ToyVpnClient extends Activity implements View.OnClickListener {
|
||||
private TextView mServerAddress;
|
||||
private TextView mServerPort;
|
||||
private TextView mSharedSecret;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.form);
|
||||
|
||||
mServerAddress = (TextView) findViewById(R.id.address);
|
||||
mServerPort = (TextView) findViewById(R.id.port);
|
||||
mSharedSecret = (TextView) findViewById(R.id.secret);
|
||||
|
||||
findViewById(R.id.connect).setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = VpnService.prepare(this);
|
||||
if (intent != null) {
|
||||
startActivityForResult(intent, 0);
|
||||
} else {
|
||||
onActivityResult(0, RESULT_OK, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int request, int result, Intent data) {
|
||||
if (result == RESULT_OK) {
|
||||
String prefix = getPackageName();
|
||||
Intent intent = new Intent(this, ToyVpnService.class)
|
||||
.putExtra(prefix + ".ADDRESS", mServerAddress.getText().toString())
|
||||
.putExtra(prefix + ".PORT", mServerPort.getText().toString())
|
||||
.putExtra(prefix + ".SECRET", mSharedSecret.getText().toString());
|
||||
startService(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
337
samples/ToyVpn/src/com/example/android/toyvpn/ToyVpnService.java
Normal file
337
samples/ToyVpn/src/com/example/android/toyvpn/ToyVpnService.java
Normal file
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.toyvpn;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.net.VpnService;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.DatagramChannel;
|
||||
|
||||
public class ToyVpnService extends VpnService implements Handler.Callback, Runnable {
|
||||
private static final String TAG = "ToyVpnService";
|
||||
|
||||
private String mServerAddress;
|
||||
private String mServerPort;
|
||||
private byte[] mSharedSecret;
|
||||
private PendingIntent mConfigureIntent;
|
||||
|
||||
private Handler mHandler;
|
||||
private Thread mThread;
|
||||
|
||||
private ParcelFileDescriptor mInterface;
|
||||
private String mParameters;
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
// The handler is only used to show messages.
|
||||
if (mHandler == null) {
|
||||
mHandler = new Handler(this);
|
||||
}
|
||||
|
||||
// Stop the previous session by interrupting the thread.
|
||||
if (mThread != null) {
|
||||
mThread.interrupt();
|
||||
}
|
||||
|
||||
// Extract information from the intent.
|
||||
String prefix = getPackageName();
|
||||
mServerAddress = intent.getStringExtra(prefix + ".ADDRESS");
|
||||
mServerPort = intent.getStringExtra(prefix + ".PORT");
|
||||
mSharedSecret = intent.getStringExtra(prefix + ".SECRET").getBytes();
|
||||
|
||||
// Start a new session by creating a new thread.
|
||||
mThread = new Thread(this, "ToyVpnThread");
|
||||
mThread.start();
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (mThread != null) {
|
||||
mThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message message) {
|
||||
if (message != null) {
|
||||
Toast.makeText(this, message.what, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void run() {
|
||||
try {
|
||||
Log.i(TAG, "Starting");
|
||||
|
||||
// If anything needs to be obtained using the network, get it now.
|
||||
// This greatly reduces the complexity of seamless handover, which
|
||||
// tries to recreate the tunnel without shutting down everything.
|
||||
// In this demo, all we need to know is the server address.
|
||||
InetSocketAddress server = new InetSocketAddress(
|
||||
mServerAddress, Integer.parseInt(mServerPort));
|
||||
|
||||
// We try to create the tunnel for several times. The better way
|
||||
// is to work with ConnectivityManager, such as trying only when
|
||||
// the network is avaiable. Here we just use a counter to keep
|
||||
// things simple.
|
||||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||||
mHandler.sendEmptyMessage(R.string.connecting);
|
||||
|
||||
// Reset the counter if we were connected.
|
||||
if (run(server)) {
|
||||
attempt = 0;
|
||||
}
|
||||
|
||||
// Sleep for a while. This also checks if we got interrupted.
|
||||
Thread.sleep(3000);
|
||||
}
|
||||
Log.i(TAG, "Giving up");
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got " + e.toString());
|
||||
} finally {
|
||||
try {
|
||||
mInterface.close();
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
mInterface = null;
|
||||
mParameters = null;
|
||||
|
||||
mHandler.sendEmptyMessage(R.string.disconnected);
|
||||
Log.i(TAG, "Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean run(InetSocketAddress server) throws Exception {
|
||||
DatagramChannel tunnel = null;
|
||||
boolean connected = false;
|
||||
try {
|
||||
// Create a DatagramChannel as the VPN tunnel.
|
||||
tunnel = DatagramChannel.open();
|
||||
|
||||
// Protect the tunnel before connecting to avoid loopback.
|
||||
if (!protect(tunnel.socket())) {
|
||||
throw new IllegalStateException("Cannot protect the tunnel");
|
||||
}
|
||||
|
||||
// Connect to the server.
|
||||
tunnel.connect(server);
|
||||
|
||||
// For simplicity, we use the same thread for both reading and
|
||||
// writing. Here we put the tunnel into non-blocking mode.
|
||||
tunnel.configureBlocking(false);
|
||||
|
||||
// Authenticate and configure the virtual network interface.
|
||||
handshake(tunnel);
|
||||
|
||||
// Now we are connected. Set the flag and show the message.
|
||||
connected = true;
|
||||
mHandler.sendEmptyMessage(R.string.connected);
|
||||
|
||||
// Packets to be sent are queued in this input stream.
|
||||
FileInputStream in = new FileInputStream(mInterface.getFileDescriptor());
|
||||
|
||||
// Packets received need to be written to this output stream.
|
||||
FileOutputStream out = new FileOutputStream(mInterface.getFileDescriptor());
|
||||
|
||||
// Allocate the buffer for a single packet.
|
||||
ByteBuffer packet = ByteBuffer.allocate(32767);
|
||||
|
||||
// We use a timer to determine the status of the tunnel. It
|
||||
// works on both sides. A positive value means sending, and
|
||||
// any other means receiving. We start with receiving.
|
||||
int timer = 0;
|
||||
|
||||
// We keep forwarding packets till something goes wrong.
|
||||
while (true) {
|
||||
// Assume that we did not make any progress in this iteration.
|
||||
boolean idle = true;
|
||||
|
||||
// Read the outgoing packet from the input stream.
|
||||
int length = in.read(packet.array());
|
||||
if (length > 0) {
|
||||
// Write the outgoing packet to the tunnel.
|
||||
packet.limit(length);
|
||||
tunnel.write(packet);
|
||||
packet.clear();
|
||||
|
||||
// There might be more outgoing packets.
|
||||
idle = false;
|
||||
|
||||
// If we were receiving, switch to sending.
|
||||
if (timer < 1) {
|
||||
timer = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the incoming packet from the tunnel.
|
||||
length = tunnel.read(packet);
|
||||
if (length > 0) {
|
||||
// Ignore control messages, which start with zero.
|
||||
if (packet.get(0) != 0) {
|
||||
// Write the incoming packet to the output stream.
|
||||
out.write(packet.array(), 0, length);
|
||||
}
|
||||
packet.clear();
|
||||
|
||||
// There might be more incoming packets.
|
||||
idle = false;
|
||||
|
||||
// If we were sending, switch to receiving.
|
||||
if (timer > 0) {
|
||||
timer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// If we are idle or waiting for the network, sleep for a
|
||||
// fraction of time to avoid busy looping.
|
||||
if (idle) {
|
||||
Thread.sleep(100);
|
||||
|
||||
// Increase the timer. This is inaccurate but good enough,
|
||||
// since everything is operated in non-blocking mode.
|
||||
timer += (timer > 0) ? 100 : -100;
|
||||
|
||||
// We are receiving for a long time but not sending.
|
||||
if (timer < -15000) {
|
||||
// Send empty control messages.
|
||||
packet.put((byte) 0).limit(1);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
packet.position(0);
|
||||
tunnel.write(packet);
|
||||
}
|
||||
packet.clear();
|
||||
|
||||
// Switch to sending.
|
||||
timer = 1;
|
||||
}
|
||||
|
||||
// We are sending for a long time but not receiving.
|
||||
if (timer > 20000) {
|
||||
throw new IllegalStateException("Timed out");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got " + e.toString());
|
||||
} finally {
|
||||
try {
|
||||
tunnel.close();
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return connected;
|
||||
}
|
||||
|
||||
private void handshake(DatagramChannel tunnel) throws Exception {
|
||||
// To build a secured tunnel, we should perform mutual authentication
|
||||
// and exchange session keys for encryption. To keep things simple in
|
||||
// this demo, we just send the shared secret in plaintext and wait
|
||||
// for the server to send the parameters.
|
||||
|
||||
// Allocate the buffer for handshaking.
|
||||
ByteBuffer packet = ByteBuffer.allocate(1024);
|
||||
|
||||
// Control messages always start with zero.
|
||||
packet.put((byte) 0).put(mSharedSecret).flip();
|
||||
|
||||
// Send the secret several times in case of packet loss.
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
packet.position(0);
|
||||
tunnel.write(packet);
|
||||
}
|
||||
packet.clear();
|
||||
|
||||
// Wait for the parameters within a limited time.
|
||||
for (int i = 0; i < 50; ++i) {
|
||||
Thread.sleep(100);
|
||||
|
||||
// Normally we should not receive random packets.
|
||||
int length = tunnel.read(packet);
|
||||
if (length > 0 && packet.get(0) == 0) {
|
||||
configure(new String(packet.array(), 1, length - 1).trim());
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Timed out");
|
||||
}
|
||||
|
||||
private void configure(String parameters) throws Exception {
|
||||
// If the old interface has exactly the same parameters, use it!
|
||||
if (mInterface != null && parameters.equals(mParameters)) {
|
||||
Log.i(TAG, "Using the previous interface");
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure a builder while parsing the parameters.
|
||||
Builder builder = new Builder();
|
||||
for (String parameter : parameters.split(" ")) {
|
||||
String[] fields = parameter.split(",");
|
||||
try {
|
||||
switch (fields[0].charAt(0)) {
|
||||
case 'm':
|
||||
builder.setMtu(Short.parseShort(fields[1]));
|
||||
break;
|
||||
case 'a':
|
||||
builder.addAddress(fields[1], Integer.parseInt(fields[2]));
|
||||
break;
|
||||
case 'r':
|
||||
builder.addRoute(fields[1], Integer.parseInt(fields[2]));
|
||||
break;
|
||||
case 'd':
|
||||
builder.addDnsServer(fields[1]);
|
||||
break;
|
||||
case 's':
|
||||
builder.addSearchDomain(fields[1]);
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Bad parameter: " + parameter);
|
||||
}
|
||||
}
|
||||
|
||||
// Close the old interface since the parameters have been changed.
|
||||
try {
|
||||
mInterface.close();
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// Create a new interface using the builder and save the parameters.
|
||||
mInterface = builder.setSession(mServerAddress)
|
||||
.setConfigureIntent(mConfigureIntent)
|
||||
.establish();
|
||||
mParameters = parameters;
|
||||
Log.i(TAG, "New interface: " + parameters);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user