Merge "Android VPN sample for ICS SDK" into ics-mr0
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/TicTacToeLib samples/${PLATFORM_NAME}/TicTacToeLib
|
||||||
development/samples/TicTacToeMain samples/${PLATFORM_NAME}/TicTacToeMain
|
development/samples/TicTacToeMain samples/${PLATFORM_NAME}/TicTacToeMain
|
||||||
development/samples/TtsEngine samples/${PLATFORM_NAME}/TtsEngine
|
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/MissileLauncher samples/${PLATFORM_NAME}/USB/MissileLauncher
|
||||||
development/samples/USB/AdbTest samples/${PLATFORM_NAME}/USB/AdbTest
|
development/samples/USB/AdbTest samples/${PLATFORM_NAME}/USB/AdbTest
|
||||||
development/samples/VoiceRecognitionService samples/${PLATFORM_NAME}/VoiceRecognitionService
|
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