am 51cf4f22: am 9e1fdc96: Merge "Android VPN sample for ICS SDK" into ics-mr0

* commit '51cf4f22830e7b4e52278db0772692c6db5f327d':
  Android VPN sample for ICS SDK
This commit is contained in:
Chia-chi Yeh
2011-12-01 14:06:11 -08:00
committed by Android Git Automerger
12 changed files with 866 additions and 0 deletions

View File

@@ -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
View 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))

View 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
View 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" />

View 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>

View 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>

View 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>

View 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

View 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(&parameters[offset + 1], parameter, length);
offset += 1 + length;
}
// Fill the rest of the space with spaces.
memset(&parameters[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);
}

View File

@@ -0,0 +1 @@
<p>Server code can be found in the ToyVPN sample distributed in the SDK.</p>

View File

@@ -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);
}
}
}

View 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);
}
}