Merge change 3424 into donut
* changes: Add the VPN services package, VPN service base classes and L2tpIpsecService.
This commit is contained in:
16
packages/VpnServices/Android.mk
Normal file
16
packages/VpnServices/Android.mk
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
LOCAL_PATH:= $(call my-dir)
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_MODULE_TAGS := user
|
||||||
|
|
||||||
|
LOCAL_SRC_FILES := $(call all-subdir-java-files)
|
||||||
|
|
||||||
|
LOCAL_JAVA_LIBRARIES :=
|
||||||
|
|
||||||
|
LOCAL_PACKAGE_NAME := VpnServices
|
||||||
|
LOCAL_CERTIFICATE := platform
|
||||||
|
|
||||||
|
include $(BUILD_PACKAGE)
|
||||||
|
|
||||||
|
########################
|
||||||
|
include $(call all-makefiles-under,$(LOCAL_PATH))
|
||||||
22
packages/VpnServices/AndroidManifest.xml
Normal file
22
packages/VpnServices/AndroidManifest.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.android.server.vpn"
|
||||||
|
android:sharedUserId="android.uid.system"
|
||||||
|
>
|
||||||
|
<application android:label="VPN Services">
|
||||||
|
|
||||||
|
<service android:name=".VpnServiceBinder" android:process=":remote">
|
||||||
|
<intent-filter>
|
||||||
|
<!-- These are the interfaces supported by the service, which
|
||||||
|
you can bind to. -->
|
||||||
|
<action android:name="android.net.vpn.IVpnService" />
|
||||||
|
<!-- This is an action code you can use to select the service
|
||||||
|
without explicitly supplying the implementation class. -->
|
||||||
|
<action android:name="android.net.vpn.SERVICE" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
|
||||||
|
</manifest>
|
||||||
0
packages/VpnServices/MODULE_LICENSE_APACHE2
Normal file
0
packages/VpnServices/MODULE_LICENSE_APACHE2
Normal file
190
packages/VpnServices/NOTICE
Normal file
190
packages/VpnServices/NOTICE
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
|
||||||
|
Copyright (c) 2005-2008, 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
BIN
packages/VpnServices/res/drawable/vpn_connected.png
Normal file
BIN
packages/VpnServices/res/drawable/vpn_connected.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 757 B |
BIN
packages/VpnServices/res/drawable/vpn_disconnected.png
Normal file
BIN
packages/VpnServices/res/drawable/vpn_disconnected.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 717 B |
8
packages/VpnServices/res/values/strings.xml
Executable file
8
packages/VpnServices/res/values/strings.xml
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="vpn_notification_title">%s VPN %s</string>
|
||||||
|
<string name="vpn_notification_connected">connected</string>
|
||||||
|
<string name="vpn_notification_disconnected">disconnected</string>
|
||||||
|
<string name="vpn_notification_connected_message">Up time: %s</string>
|
||||||
|
</resources>
|
||||||
|
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2007, 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.android.server.vpn;
|
||||||
|
|
||||||
|
import android.net.LocalSocket;
|
||||||
|
import android.net.LocalSocketAddress;
|
||||||
|
import android.os.SystemProperties;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy to start, stop and interact with an Android service defined in init.rc.
|
||||||
|
* The android service is expected to accept connection through Unix domain
|
||||||
|
* socket. When the proxy successfully starts the service, it will establish a
|
||||||
|
* socket connection with the service. The socket serves two purposes: (1) send
|
||||||
|
* commands to the service; (2) for the proxy to know whether the service is
|
||||||
|
* alive.
|
||||||
|
*
|
||||||
|
* After the service receives commands from the proxy, it should return either
|
||||||
|
* 0 if the service will close the socket (and the proxy will re-establish
|
||||||
|
* another connection immediately after), or 1 if the socket is remained alive.
|
||||||
|
*/
|
||||||
|
public class AndroidServiceProxy extends ProcessProxy {
|
||||||
|
private static final int WAITING_TIME = 15; // sec
|
||||||
|
|
||||||
|
private static final String SVC_STATE_CMD_PREFIX = "init.svc.";
|
||||||
|
private static final String SVC_START_CMD = "ctl.start";
|
||||||
|
private static final String SVC_STOP_CMD = "ctl.stop";
|
||||||
|
private static final String SVC_STATE_RUNNING = "running";
|
||||||
|
private static final String SVC_STATE_STOPPED = "stopped";
|
||||||
|
|
||||||
|
private static final int END_OF_ARGUMENTS = 255;
|
||||||
|
|
||||||
|
private String mServiceName;
|
||||||
|
private String mSocketName;
|
||||||
|
private LocalSocket mKeepaliveSocket;
|
||||||
|
private boolean mControlSocketInUse;
|
||||||
|
private Integer mSocketResult = null;
|
||||||
|
private String mTag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a proxy with the service name.
|
||||||
|
* @param serviceName the service name
|
||||||
|
*/
|
||||||
|
public AndroidServiceProxy(String serviceName) {
|
||||||
|
mServiceName = serviceName;
|
||||||
|
mSocketName = serviceName;
|
||||||
|
mTag = "SProxy_" + serviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "Service " + mServiceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void stop() {
|
||||||
|
if (isRunning()) setResultAndCloseControlSocket(-1);
|
||||||
|
SystemProperties.set(SVC_STOP_CMD, mServiceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a command with arguments to the service through the control socket.
|
||||||
|
* Each argument is sent as a C-style zero-terminated string.
|
||||||
|
*/
|
||||||
|
public void sendCommand(String ...args) throws IOException {
|
||||||
|
OutputStream out = getControlSocketOutput();
|
||||||
|
for (String arg : args) outputString(out, arg);
|
||||||
|
checkSocketResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a command with arguments to the service through the control socket.
|
||||||
|
*/
|
||||||
|
public void sendCommand2(String ...args) throws IOException {
|
||||||
|
OutputStream out = getControlSocketOutput();
|
||||||
|
for (String arg : args) outputString2(out, arg);
|
||||||
|
out.write(END_OF_ARGUMENTS);
|
||||||
|
out.flush();
|
||||||
|
checkSocketResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
* The method returns when the service exits.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void performTask() throws IOException {
|
||||||
|
String svc = mServiceName;
|
||||||
|
Log.d(mTag, "+++++ Execute: " + svc);
|
||||||
|
SystemProperties.set(SVC_START_CMD, svc);
|
||||||
|
|
||||||
|
boolean success = blockUntil(SVC_STATE_RUNNING, WAITING_TIME);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
Log.d(mTag, "----- Running: " + svc + ", create keepalive socket");
|
||||||
|
LocalSocket s = mKeepaliveSocket = createServiceSocket();
|
||||||
|
setState(ProcessState.RUNNING);
|
||||||
|
|
||||||
|
if (s == null) {
|
||||||
|
// no socket connection, stop hosting the service
|
||||||
|
stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
for (;;) {
|
||||||
|
InputStream in = s.getInputStream();
|
||||||
|
int data = in.read();
|
||||||
|
if (data >= 0) {
|
||||||
|
Log.d(mTag, "got data from keepalive socket: " + data);
|
||||||
|
|
||||||
|
if (data == 0) {
|
||||||
|
// re-establish the connection:
|
||||||
|
// synchronized here so that checkSocketResult() returns
|
||||||
|
// when new mKeepaliveSocket is available for next cmd
|
||||||
|
synchronized (this) {
|
||||||
|
setResultAndCloseControlSocket((byte) data);
|
||||||
|
s = mKeepaliveSocket = createServiceSocket();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// keep the socket
|
||||||
|
setSocketResult(data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// service is gone
|
||||||
|
if (mControlSocketInUse) setSocketResult(-1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(mTag, "keepalive connection closed");
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.d(mTag, "keepalive socket broken: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait 5 seconds for the service to exit
|
||||||
|
success = blockUntil(SVC_STATE_STOPPED, 5);
|
||||||
|
Log.d(mTag, "stopping " + svc + ", success? " + success);
|
||||||
|
} else {
|
||||||
|
setState(ProcessState.STOPPED);
|
||||||
|
throw new IOException("cannot start service: " + svc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalSocket createServiceSocket() throws IOException {
|
||||||
|
LocalSocket s = new LocalSocket();
|
||||||
|
LocalSocketAddress a = new LocalSocketAddress(mSocketName,
|
||||||
|
LocalSocketAddress.Namespace.RESERVED);
|
||||||
|
|
||||||
|
// try a few times in case the service has not listen()ed
|
||||||
|
IOException excp = null;
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
try {
|
||||||
|
s.connect(a);
|
||||||
|
return s;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.d(mTag, "service not yet listen()ing; try again");
|
||||||
|
excp = e;
|
||||||
|
sleep(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw excp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private OutputStream getControlSocketOutput() throws IOException {
|
||||||
|
if (mKeepaliveSocket != null) {
|
||||||
|
mControlSocketInUse = true;
|
||||||
|
mSocketResult = null;
|
||||||
|
return mKeepaliveSocket.getOutputStream();
|
||||||
|
} else {
|
||||||
|
throw new IOException("no control socket available");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void checkSocketResult() throws IOException {
|
||||||
|
try {
|
||||||
|
// will be notified when the result comes back from service
|
||||||
|
if (mSocketResult == null) wait();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.d(mTag, "checkSocketResult(): " + e);
|
||||||
|
} finally {
|
||||||
|
mControlSocketInUse = false;
|
||||||
|
if ((mSocketResult == null) || (mSocketResult < 0)) {
|
||||||
|
throw new IOException("socket error, result from service: "
|
||||||
|
+ mSocketResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void setSocketResult(int result) {
|
||||||
|
if (mControlSocketInUse) {
|
||||||
|
mSocketResult = result;
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setResultAndCloseControlSocket(int result) {
|
||||||
|
setSocketResult(result);
|
||||||
|
try {
|
||||||
|
mKeepaliveSocket.shutdownInput();
|
||||||
|
mKeepaliveSocket.shutdownOutput();
|
||||||
|
mKeepaliveSocket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(mTag, "close keepalive socket", e);
|
||||||
|
} finally {
|
||||||
|
mKeepaliveSocket = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for the process to be in the expected state. The method returns
|
||||||
|
* false if after the specified duration (in seconds), the process is still
|
||||||
|
* not in the expected state.
|
||||||
|
*/
|
||||||
|
private boolean blockUntil(String expectedState, int waitTime) {
|
||||||
|
String cmd = SVC_STATE_CMD_PREFIX + mServiceName;
|
||||||
|
int sleepTime = 200; // ms
|
||||||
|
int n = waitTime * 1000 / sleepTime;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (expectedState.equals(SystemProperties.get(cmd))) {
|
||||||
|
Log.d(mTag, mServiceName + " is " + expectedState + " after "
|
||||||
|
+ (i * sleepTime) + " msec");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sleep(sleepTime);
|
||||||
|
}
|
||||||
|
return expectedState.equals(SystemProperties.get(cmd));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void outputString(OutputStream out, String s) throws IOException {
|
||||||
|
out.write(s.getBytes());
|
||||||
|
out.write(0);
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void outputString2(OutputStream out, String s) throws IOException {
|
||||||
|
byte[] bytes = s.getBytes();
|
||||||
|
out.write(bytes.length);
|
||||||
|
out.write(bytes);
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2007, 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.android.server.vpn;
|
||||||
|
|
||||||
|
import android.net.vpn.L2tpIpsecProfile;
|
||||||
|
import android.security.Keystore;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The service that manages the L2TP-over-IPSec VPN connection.
|
||||||
|
*/
|
||||||
|
class L2tpIpsecService extends VpnService<L2tpIpsecProfile> {
|
||||||
|
private static final String IPSEC_SERVICE = "racoon";
|
||||||
|
private static final String L2TP_SERVICE = "mtpd";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void connect(String serverIp, String username, String password)
|
||||||
|
throws IOException {
|
||||||
|
String hostIp = getHostIp();
|
||||||
|
|
||||||
|
// IPSEC
|
||||||
|
AndroidServiceProxy ipsecService = startService(IPSEC_SERVICE);
|
||||||
|
ipsecService.sendCommand(
|
||||||
|
String.format("SETKEY %s %s", hostIp, serverIp));
|
||||||
|
ipsecService.sendCommand(String.format("SET_CERTS %s %s %s %s",
|
||||||
|
serverIp, getCaCertPath(), getUserCertPath(),
|
||||||
|
getUserkeyPath()));
|
||||||
|
|
||||||
|
// L2TP
|
||||||
|
AndroidServiceProxy l2tpService = startService(L2TP_SERVICE);
|
||||||
|
l2tpService.sendCommand2("l2tp", serverIp, "1701", "",
|
||||||
|
"file", getPppOptionFilePath(),
|
||||||
|
"name", username,
|
||||||
|
"password", password);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCaCertPath() {
|
||||||
|
return Keystore.getInstance().getCertificate(
|
||||||
|
getProfile().getCaCertificate());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getUserCertPath() {
|
||||||
|
return Keystore.getInstance().getCertificate(
|
||||||
|
getProfile().getUserCertificate());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getUserkeyPath() {
|
||||||
|
return Keystore.getInstance().getUserkey(getProfile().getUserkey());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2007, 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.android.server.vpn;
|
||||||
|
|
||||||
|
import android.net.vpn.L2tpProfile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The service that manages the L2TP VPN connection.
|
||||||
|
*/
|
||||||
|
class L2tpService extends VpnService<L2tpProfile> {
|
||||||
|
private static final String L2TP_SERVICE = "mtpd";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void connect(String serverIp, String username, String password)
|
||||||
|
throws IOException {
|
||||||
|
String hostIp = getHostIp();
|
||||||
|
|
||||||
|
// L2TP
|
||||||
|
AndroidServiceProxy l2tpService = startService(L2TP_SERVICE);
|
||||||
|
l2tpService.sendCommand2("l2tp", serverIp, "1701", "",
|
||||||
|
"file", getPppOptionFilePath(),
|
||||||
|
"name", username,
|
||||||
|
"password", password);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2007, 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.android.server.vpn;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy to perform a command with arguments.
|
||||||
|
*/
|
||||||
|
public class NormalProcessProxy extends ProcessProxy {
|
||||||
|
private Process mProcess;
|
||||||
|
private String[] mArgs;
|
||||||
|
private String mTag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a proxy with the arguments.
|
||||||
|
* @param args the argument list with the first one being the command
|
||||||
|
*/
|
||||||
|
public NormalProcessProxy(String ...args) {
|
||||||
|
if ((args == null) || (args.length == 0)) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
mArgs = args;
|
||||||
|
mTag = "PProxy_" + getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return mArgs[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void stop() {
|
||||||
|
if (isStopped()) return;
|
||||||
|
getHostThread().interrupt();
|
||||||
|
// TODO: not sure how to reliably kill a process
|
||||||
|
mProcess.destroy();
|
||||||
|
setState(ProcessState.STOPPING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void performTask() throws IOException, InterruptedException {
|
||||||
|
String[] args = mArgs;
|
||||||
|
Log.d(mTag, "+++++ Execute: " + getEntireCommand());
|
||||||
|
ProcessBuilder pb = new ProcessBuilder(args);
|
||||||
|
setState(ProcessState.RUNNING);
|
||||||
|
Process p = mProcess = pb.start();
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(
|
||||||
|
p.getInputStream()));
|
||||||
|
while (true) {
|
||||||
|
String line = reader.readLine();
|
||||||
|
if ((line == null) || isStopping()) break;
|
||||||
|
Log.d(mTag, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(mTag, "----- p.waitFor(): " + getName());
|
||||||
|
p.waitFor();
|
||||||
|
Log.d(mTag, "----- Done: " + getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private CharSequence getEntireCommand() {
|
||||||
|
String[] args = mArgs;
|
||||||
|
StringBuilder sb = new StringBuilder(args[0]);
|
||||||
|
for (int i = 1; i < args.length; i++) sb.append(' ').append(args[i]);
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2007, 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.android.server.vpn;
|
||||||
|
|
||||||
|
import android.os.ConditionVariable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A proxy class that spawns a process to accomplish a certain task.
|
||||||
|
*/
|
||||||
|
public abstract class ProcessProxy {
|
||||||
|
/**
|
||||||
|
* Defines the interface to call back when the process is finished or an
|
||||||
|
* error occurs during execution.
|
||||||
|
*/
|
||||||
|
public static interface Callback {
|
||||||
|
/**
|
||||||
|
* Called when the process is finished.
|
||||||
|
* @param proxy the proxy that hosts the process
|
||||||
|
*/
|
||||||
|
void done(ProcessProxy proxy);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when some error occurs.
|
||||||
|
* @param proxy the proxy that hosts the process
|
||||||
|
*/
|
||||||
|
void error(ProcessProxy proxy, Throwable error);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected enum ProcessState {
|
||||||
|
STOPPED, STARTING, RUNNING, STOPPING, ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProcessState mState = ProcessState.STOPPED;
|
||||||
|
private ConditionVariable mLock = new ConditionVariable();
|
||||||
|
private Thread mThread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the process.
|
||||||
|
*/
|
||||||
|
public abstract String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the process with a callback.
|
||||||
|
* @param callback the callback to get notified when the process is finished
|
||||||
|
* or an error occurs during execution
|
||||||
|
* @throws IOException when the process is already running or failed to
|
||||||
|
* start
|
||||||
|
*/
|
||||||
|
public synchronized void start(final Callback callback) throws IOException {
|
||||||
|
if (!isStopped()) {
|
||||||
|
throw new IOException("Process is already running: " + this);
|
||||||
|
}
|
||||||
|
mLock.close();
|
||||||
|
setState(ProcessState.STARTING);
|
||||||
|
Thread thread = new Thread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
performTask();
|
||||||
|
setState(ProcessState.STOPPED);
|
||||||
|
mLock.open();
|
||||||
|
if (callback != null) callback.done(ProcessProxy.this);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
setState(ProcessState.ERROR);
|
||||||
|
if (callback != null) callback.error(ProcessProxy.this, e);
|
||||||
|
} finally {
|
||||||
|
mThread = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.setPriority(Thread.MIN_PRIORITY);
|
||||||
|
thread.start();
|
||||||
|
mThread = thread;
|
||||||
|
if (!waitUntilRunning()) {
|
||||||
|
throw new IOException("Failed to start the process: " + this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the process.
|
||||||
|
* @throws IOException when the process is already running or failed to
|
||||||
|
* start
|
||||||
|
*/
|
||||||
|
public synchronized void start() throws IOException {
|
||||||
|
start(null);
|
||||||
|
if (!waitUntilDone()) {
|
||||||
|
throw new IOException("Failed to complete the process: " + this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the thread that hosts the process.
|
||||||
|
*/
|
||||||
|
public Thread getHostThread() {
|
||||||
|
return mThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocks the current thread until the hosted process is finished.
|
||||||
|
*
|
||||||
|
* @return true if the process is finished normally; false if an error
|
||||||
|
* occurs
|
||||||
|
*/
|
||||||
|
public boolean waitUntilDone() {
|
||||||
|
while (!mLock.block(1000)) {
|
||||||
|
if (isStopped() || isInError()) break;
|
||||||
|
}
|
||||||
|
return isStopped();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocks the current thread until the hosted process is running.
|
||||||
|
*
|
||||||
|
* @return true if the process is running normally; false if the process
|
||||||
|
* is in another state
|
||||||
|
*/
|
||||||
|
private boolean waitUntilRunning() {
|
||||||
|
for (;;) {
|
||||||
|
if (!isStarting()) break;
|
||||||
|
}
|
||||||
|
return isRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops and destroys the process.
|
||||||
|
*/
|
||||||
|
public abstract void stop();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the process is finished.
|
||||||
|
* @return true if the process is stopped
|
||||||
|
*/
|
||||||
|
public boolean isStopped() {
|
||||||
|
return (mState == ProcessState.STOPPED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the process is being stopped.
|
||||||
|
* @return true if the process is being stopped
|
||||||
|
*/
|
||||||
|
public boolean isStopping() {
|
||||||
|
return (mState == ProcessState.STOPPING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the process is being started.
|
||||||
|
* @return true if the process is being started
|
||||||
|
*/
|
||||||
|
public boolean isStarting() {
|
||||||
|
return (mState == ProcessState.STARTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the process is running.
|
||||||
|
* @return true if the process is running
|
||||||
|
*/
|
||||||
|
public boolean isRunning() {
|
||||||
|
return (mState == ProcessState.RUNNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether some error has occurred and the process is stopped.
|
||||||
|
* @return true if some error has occurred and the process is stopped
|
||||||
|
*/
|
||||||
|
public boolean isInError() {
|
||||||
|
return (mState == ProcessState.ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the actual task. Subclasses must make sure that the method
|
||||||
|
* is blocked until the task is done or an error occurs.
|
||||||
|
*/
|
||||||
|
protected abstract void performTask()
|
||||||
|
throws IOException, InterruptedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the process state.
|
||||||
|
* @param state the new state to be in
|
||||||
|
*/
|
||||||
|
protected void setState(ProcessState state) {
|
||||||
|
mState = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the current thread sleep for the specified time.
|
||||||
|
* @param msec time to sleep in miliseconds
|
||||||
|
*/
|
||||||
|
protected void sleep(int msec) {
|
||||||
|
try {
|
||||||
|
Thread.currentThread().sleep(msec);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
627
packages/VpnServices/src/com/android/server/vpn/VpnService.java
Normal file
627
packages/VpnServices/src/com/android/server/vpn/VpnService.java
Normal file
@@ -0,0 +1,627 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2007, 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.android.server.vpn;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.vpn.SingleServerProfile;
|
||||||
|
import android.net.vpn.VpnManager;
|
||||||
|
import android.net.vpn.VpnState;
|
||||||
|
import android.os.FileObserver;
|
||||||
|
import android.os.SystemProperties;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The service base class for managing a type of VPN connection.
|
||||||
|
*/
|
||||||
|
abstract class VpnService<E extends SingleServerProfile> {
|
||||||
|
private static final int NOTIFICATION_ID = 1;
|
||||||
|
private static final String PROFILES_ROOT = VpnManager.PROFILES_PATH + "/";
|
||||||
|
public static final String DEFAULT_CONFIG_PATH = "/etc";
|
||||||
|
|
||||||
|
private static final int DNS_TIMEOUT = 3000; // ms
|
||||||
|
private static final String DNS1 = "net.dns1";
|
||||||
|
private static final String DNS2 = "net.dns2";
|
||||||
|
private static final String REMOTE_IP = "net.ipremote";
|
||||||
|
private static final String DNS_DOMAIN_SUFFICES = "net.dns.search";
|
||||||
|
private static final String SERVER_IP = "net.vpn.server_ip";
|
||||||
|
|
||||||
|
private static final int VPN_TIMEOUT = 30000; // milliseconds
|
||||||
|
private static final int ONE_SECOND = 1000; // milliseconds
|
||||||
|
private static final int FIVE_SECOND = 5000; // milliseconds
|
||||||
|
|
||||||
|
private static final String LOGWRAPPER = "/system/bin/logwrapper";
|
||||||
|
private final String TAG = VpnService.class.getSimpleName();
|
||||||
|
|
||||||
|
E mProfile;
|
||||||
|
VpnServiceBinder mContext;
|
||||||
|
|
||||||
|
private VpnState mState = VpnState.IDLE;
|
||||||
|
private boolean mInError;
|
||||||
|
|
||||||
|
// connection settings
|
||||||
|
private String mOriginalDns1;
|
||||||
|
private String mOriginalDns2;
|
||||||
|
private String mVpnDns1 = "";
|
||||||
|
private String mVpnDns2 = "";
|
||||||
|
private String mOriginalDomainSuffices;
|
||||||
|
private String mHostIp;
|
||||||
|
|
||||||
|
private long mStartTime; // VPN connection start time
|
||||||
|
|
||||||
|
// monitors if the VPN connection is sucessfully established
|
||||||
|
private FileMonitor mConnectMonitor;
|
||||||
|
|
||||||
|
// watch dog timer; fired up if the connection cannot be established within
|
||||||
|
// VPN_TIMEOUT
|
||||||
|
private Object mWatchdog;
|
||||||
|
|
||||||
|
// for helping managing multiple Android services
|
||||||
|
private ServiceHelper mServiceHelper = new ServiceHelper();
|
||||||
|
|
||||||
|
// for helping showing, updating notification
|
||||||
|
private NotificationHelper mNotification = new NotificationHelper();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establishes a VPN connection with the specified username and password.
|
||||||
|
*/
|
||||||
|
protected abstract void connect(String serverIp, String username,
|
||||||
|
String password) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tears down the VPN connection. The base class simply terminates all the
|
||||||
|
* Android services. A subclass may need to do some clean-up before that.
|
||||||
|
*/
|
||||||
|
protected void disconnect() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an Android service defined in init.rc.
|
||||||
|
*/
|
||||||
|
protected AndroidServiceProxy startService(String serviceName)
|
||||||
|
throws IOException {
|
||||||
|
return mServiceHelper.startService(serviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getPppOptionFilePath() throws IOException {
|
||||||
|
String subpath = getProfileSubpath("/ppp/peers");
|
||||||
|
String[] kids = new File(subpath).list();
|
||||||
|
if ((kids == null) || (kids.length == 0)) {
|
||||||
|
throw new IOException("no option file found in " + subpath);
|
||||||
|
}
|
||||||
|
if (kids.length > 1) {
|
||||||
|
Log.w(TAG, "more than one option file found in " + subpath
|
||||||
|
+ ", arbitrarily choose " + kids[0]);
|
||||||
|
}
|
||||||
|
return subpath + "/" + kids[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the VPN profile associated with the connection.
|
||||||
|
*/
|
||||||
|
protected E getProfile() {
|
||||||
|
return mProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the profile path where configuration files reside.
|
||||||
|
*/
|
||||||
|
protected String getProfilePath() throws IOException {
|
||||||
|
String path = PROFILES_ROOT + mProfile.getId();
|
||||||
|
File dir = new File(path);
|
||||||
|
if (!dir.exists()) throw new IOException("Profile dir does not exist");
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the path where default configuration files reside.
|
||||||
|
*/
|
||||||
|
protected String getDefaultConfigPath() throws IOException {
|
||||||
|
return DEFAULT_CONFIG_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the host IP for establishing the VPN connection.
|
||||||
|
*/
|
||||||
|
protected String getHostIp() throws IOException {
|
||||||
|
if (mHostIp == null) mHostIp = reallyGetHostIp();
|
||||||
|
return mHostIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the IP of the specified host name.
|
||||||
|
*/
|
||||||
|
protected String getIp(String hostName) throws IOException {
|
||||||
|
InetAddress iaddr = InetAddress.getByName(hostName);
|
||||||
|
byte[] aa = iaddr.getAddress();
|
||||||
|
StringBuilder sb = new StringBuilder().append(byteToInt(aa[0]));
|
||||||
|
for (int i = 1; i < aa.length; i++) {
|
||||||
|
sb.append(".").append(byteToInt(aa[i]));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the path of the script file that is executed when the VPN
|
||||||
|
* connection is established.
|
||||||
|
*/
|
||||||
|
protected String getConnectMonitorFile() {
|
||||||
|
return "/etc/ppp/ip-up";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the system property. The method is blocked until the value is
|
||||||
|
* settled in.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param value the value of the property
|
||||||
|
* @throws IOException if it fails to set the property within 2 seconds
|
||||||
|
*/
|
||||||
|
protected void setSystemProperty(String name, String value)
|
||||||
|
throws IOException {
|
||||||
|
SystemProperties.set(name, value);
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
String v = SystemProperties.get(name);
|
||||||
|
if (v.equals(value)) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "sys_prop: wait for " + name + " to settle in");
|
||||||
|
sleep(400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IOException("Failed to set system property: " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setContext(VpnServiceBinder context, E profile) {
|
||||||
|
mContext = context;
|
||||||
|
mProfile = profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
VpnState getState() {
|
||||||
|
return mState;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void onConnect(String username, String password)
|
||||||
|
throws IOException {
|
||||||
|
mState = VpnState.CONNECTING;
|
||||||
|
broadcastConnectivity(VpnState.CONNECTING);
|
||||||
|
|
||||||
|
String serverIp = getIp(getProfile().getServerName());
|
||||||
|
setSystemProperty(SERVER_IP, serverIp);
|
||||||
|
onBeforeConnect();
|
||||||
|
|
||||||
|
connect(serverIp, username, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void onDisconnect(boolean cleanUpServices) {
|
||||||
|
try {
|
||||||
|
mState = VpnState.DISCONNECTING;
|
||||||
|
broadcastConnectivity(VpnState.DISCONNECTING);
|
||||||
|
mNotification.showDisconnect();
|
||||||
|
|
||||||
|
// subclass implementation
|
||||||
|
if (cleanUpServices) disconnect();
|
||||||
|
|
||||||
|
mServiceHelper.stop();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Log.e(TAG, "onError()", e);
|
||||||
|
onFinalCleanUp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void onError() {
|
||||||
|
// error may occur during or after connection setup
|
||||||
|
// and it may be due to one or all services gone
|
||||||
|
mInError = true;
|
||||||
|
switch (mState) {
|
||||||
|
case CONNECTED:
|
||||||
|
onDisconnect(true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CONNECTING:
|
||||||
|
onDisconnect(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createConnectMonitor() {
|
||||||
|
mConnectMonitor = new FileMonitor(getConnectMonitorFile(),
|
||||||
|
new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
onConnectMonitorTriggered();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onBeforeConnect() {
|
||||||
|
mNotification.disableNotification();
|
||||||
|
|
||||||
|
createConnectMonitor();
|
||||||
|
mConnectMonitor.startWatching();
|
||||||
|
saveOriginalDnsProperties();
|
||||||
|
|
||||||
|
mWatchdog = startTimer(VPN_TIMEOUT, new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
synchronized (VpnService.this) {
|
||||||
|
if (mState == VpnState.CONNECTING) {
|
||||||
|
Log.d(TAG, " watchdog timer is fired !!");
|
||||||
|
onError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void onConnectMonitorTriggered() {
|
||||||
|
Log.d(TAG, "onConnectMonitorTriggered()");
|
||||||
|
|
||||||
|
stopTimer(mWatchdog);
|
||||||
|
mConnectMonitor.stopWatching();
|
||||||
|
saveVpnDnsProperties();
|
||||||
|
saveAndSetDomainSuffices();
|
||||||
|
startConnectivityMonitor();
|
||||||
|
|
||||||
|
mState = VpnState.CONNECTED;
|
||||||
|
broadcastConnectivity(VpnState.CONNECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void onFinalCleanUp() {
|
||||||
|
Log.d(TAG, "onFinalCleanUp()");
|
||||||
|
|
||||||
|
if (mState == VpnState.IDLE) return;
|
||||||
|
|
||||||
|
// keep the notification when error occurs
|
||||||
|
if (!mInError) mNotification.disableNotification();
|
||||||
|
|
||||||
|
restoreOriginalDnsProperties();
|
||||||
|
restoreOriginalDomainSuffices();
|
||||||
|
if (mConnectMonitor != null) mConnectMonitor.stopWatching();
|
||||||
|
if (mWatchdog != null) stopTimer(mWatchdog);
|
||||||
|
mState = VpnState.IDLE;
|
||||||
|
broadcastConnectivity(VpnState.IDLE);
|
||||||
|
|
||||||
|
// stop the service itself
|
||||||
|
mContext.stopSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void onOneServiceGone() {
|
||||||
|
switch (mState) {
|
||||||
|
case IDLE:
|
||||||
|
case DISCONNECTING:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
onError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void onAllServicesGone() {
|
||||||
|
switch (mState) {
|
||||||
|
case IDLE:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DISCONNECTING:
|
||||||
|
// daemons are gone; now clean up everything
|
||||||
|
onFinalCleanUp();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
onError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveOriginalDnsProperties() {
|
||||||
|
mOriginalDns1 = SystemProperties.get(DNS1);
|
||||||
|
mOriginalDns2 = SystemProperties.get(DNS2);
|
||||||
|
Log.d(TAG, String.format("save original dns prop: %s, %s",
|
||||||
|
mOriginalDns1, mOriginalDns2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restoreOriginalDnsProperties() {
|
||||||
|
// restore only if they are not overridden
|
||||||
|
if (mVpnDns1.equals(SystemProperties.get(DNS1))) {
|
||||||
|
Log.d(TAG, String.format("restore original dns prop: %s --> %s",
|
||||||
|
SystemProperties.get(DNS1), mOriginalDns1));
|
||||||
|
Log.d(TAG, String.format("restore original dns prop: %s --> %s",
|
||||||
|
SystemProperties.get(DNS2), mOriginalDns2));
|
||||||
|
SystemProperties.set(DNS1, mOriginalDns1);
|
||||||
|
SystemProperties.set(DNS2, mOriginalDns2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveVpnDnsProperties() {
|
||||||
|
mVpnDns1 = mVpnDns2 = "";
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
mVpnDns1 = SystemProperties.get(DNS1);
|
||||||
|
mVpnDns2 = SystemProperties.get(DNS2);
|
||||||
|
if (mVpnDns1.equals(mOriginalDns1)) {
|
||||||
|
Log.d(TAG, "wait for vpn dns to settle in..." + i);
|
||||||
|
sleep(500);
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, String.format("save vpn dns prop: %s, %s",
|
||||||
|
mVpnDns1, mVpnDns2));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.e(TAG, "saveVpnDnsProperties(): DNS not updated??");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restoreVpnDnsProperties() {
|
||||||
|
if (isNullOrEmpty(mVpnDns1) && isNullOrEmpty(mVpnDns2)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log.d(TAG, String.format("restore vpn dns prop: %s --> %s",
|
||||||
|
SystemProperties.get(DNS1), mVpnDns1));
|
||||||
|
Log.d(TAG, String.format("restore vpn dns prop: %s --> %s",
|
||||||
|
SystemProperties.get(DNS2), mVpnDns2));
|
||||||
|
SystemProperties.set(DNS1, mVpnDns1);
|
||||||
|
SystemProperties.set(DNS2, mVpnDns2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveAndSetDomainSuffices() {
|
||||||
|
mOriginalDomainSuffices = SystemProperties.get(DNS_DOMAIN_SUFFICES);
|
||||||
|
Log.d(TAG, "save original dns search: " + mOriginalDomainSuffices);
|
||||||
|
String list = mProfile.getDomainSuffices();
|
||||||
|
if (!isNullOrEmpty(list)) {
|
||||||
|
SystemProperties.set(DNS_DOMAIN_SUFFICES, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restoreOriginalDomainSuffices() {
|
||||||
|
Log.d(TAG, "restore original dns search --> " + mOriginalDomainSuffices);
|
||||||
|
SystemProperties.set(DNS_DOMAIN_SUFFICES, mOriginalDomainSuffices);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void broadcastConnectivity(VpnState s) {
|
||||||
|
new VpnManager(mContext).broadcastConnectivity(mProfile.getName(), s);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startConnectivityMonitor() {
|
||||||
|
mStartTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
Log.d(TAG, " +++++ connectivity monitor running");
|
||||||
|
try {
|
||||||
|
for (;;) {
|
||||||
|
synchronized (VpnService.this) {
|
||||||
|
if (mState != VpnState.CONNECTED) break;
|
||||||
|
mNotification.update();
|
||||||
|
checkConnectivity();
|
||||||
|
VpnService.this.wait(ONE_SECOND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.e(TAG, "connectivity monitor", e);
|
||||||
|
}
|
||||||
|
Log.d(TAG, " ----- connectivity monitor stopped");
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkConnectivity() {
|
||||||
|
checkDnsProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkDnsProperties() {
|
||||||
|
String dns1 = SystemProperties.get(DNS1);
|
||||||
|
if (!mVpnDns1.equals(dns1)) {
|
||||||
|
Log.w(TAG, " @@ !!! dns being overridden");
|
||||||
|
onError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object startTimer(final int milliseconds, final Runnable task) {
|
||||||
|
Thread thread = new Thread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
Log.d(TAG, "watchdog timer started");
|
||||||
|
Thread t = Thread.currentThread();
|
||||||
|
try {
|
||||||
|
synchronized (t) {
|
||||||
|
t.wait(milliseconds);
|
||||||
|
}
|
||||||
|
task.run();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
Log.d(TAG, "watchdog timer stopped");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.start();
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopTimer(Object timer) {
|
||||||
|
synchronized (timer) {
|
||||||
|
timer.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String reallyGetHostIp() throws IOException {
|
||||||
|
Socket s = new Socket();
|
||||||
|
s.connect(new InetSocketAddress("www.google.com", 80), DNS_TIMEOUT);
|
||||||
|
String ipAddress = s.getLocalAddress().getHostAddress();
|
||||||
|
Log.d(TAG, "Host IP: " + ipAddress);
|
||||||
|
s.close();
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getProfileSubpath(String subpath) throws IOException {
|
||||||
|
String path = getProfilePath() + subpath;
|
||||||
|
if (new File(path).exists()) {
|
||||||
|
return path;
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Profile subpath does not exist: " + path
|
||||||
|
+ ", use default one");
|
||||||
|
String path2 = getDefaultConfigPath() + subpath;
|
||||||
|
if (!new File(path2).exists()) {
|
||||||
|
throw new IOException("Profile subpath does not exist at "
|
||||||
|
+ path + " or " + path2);
|
||||||
|
}
|
||||||
|
return path2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sleep(int ms) {
|
||||||
|
try {
|
||||||
|
Thread.currentThread().sleep(ms);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isNullOrEmpty(String message) {
|
||||||
|
return ((message == null) || (message.length() == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int byteToInt(byte b) {
|
||||||
|
return ((int) b) & 0x0FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ServiceHelper implements ProcessProxy.Callback {
|
||||||
|
private List<AndroidServiceProxy> mServiceList =
|
||||||
|
new ArrayList<AndroidServiceProxy>();
|
||||||
|
|
||||||
|
// starts an Android service
|
||||||
|
AndroidServiceProxy startService(String serviceName)
|
||||||
|
throws IOException {
|
||||||
|
AndroidServiceProxy service = new AndroidServiceProxy(serviceName);
|
||||||
|
mServiceList.add(service);
|
||||||
|
service.start(this);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stops all the Android services
|
||||||
|
void stop() {
|
||||||
|
if (mServiceList.isEmpty()) {
|
||||||
|
onFinalCleanUp();
|
||||||
|
} else {
|
||||||
|
for (AndroidServiceProxy s : mServiceList) s.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//@Override
|
||||||
|
public void done(ProcessProxy p) {
|
||||||
|
Log.d(TAG, "service done: " + p.getName());
|
||||||
|
commonCallback((AndroidServiceProxy) p);
|
||||||
|
}
|
||||||
|
|
||||||
|
//@Override
|
||||||
|
public void error(ProcessProxy p, Throwable e) {
|
||||||
|
Log.e(TAG, "service error: " + p.getName(), e);
|
||||||
|
commonCallback((AndroidServiceProxy) p);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void commonCallback(AndroidServiceProxy service) {
|
||||||
|
mServiceList.remove(service);
|
||||||
|
onOneServiceGone();
|
||||||
|
if (mServiceList.isEmpty()) onAllServicesGone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FileMonitor extends FileObserver {
|
||||||
|
private Runnable mCallback;
|
||||||
|
|
||||||
|
FileMonitor(String path, Runnable callback) {
|
||||||
|
super(path, CLOSE_NOWRITE);
|
||||||
|
mCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEvent(int event, String path) {
|
||||||
|
if ((event & CLOSE_NOWRITE) > 0) mCallback.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper class for showing, updating notification.
|
||||||
|
private class NotificationHelper {
|
||||||
|
void update() {
|
||||||
|
String title = getNotificationTitle(true);
|
||||||
|
Notification n = new Notification(R.drawable.vpn_connected, title,
|
||||||
|
mStartTime);
|
||||||
|
n.setLatestEventInfo(mContext, title,
|
||||||
|
getNotificationMessage(true), prepareNotificationIntent());
|
||||||
|
n.flags |= Notification.FLAG_NO_CLEAR;
|
||||||
|
n.flags |= Notification.FLAG_ONGOING_EVENT;
|
||||||
|
enableNotification(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showDisconnect() {
|
||||||
|
String title = getNotificationTitle(false);
|
||||||
|
Notification n = new Notification(R.drawable.vpn_disconnected,
|
||||||
|
title, System.currentTimeMillis());
|
||||||
|
n.setLatestEventInfo(mContext, title,
|
||||||
|
getNotificationMessage(false), prepareNotificationIntent());
|
||||||
|
n.flags |= Notification.FLAG_AUTO_CANCEL;
|
||||||
|
disableNotification();
|
||||||
|
enableNotification(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void disableNotification() {
|
||||||
|
((NotificationManager) mContext.getSystemService(
|
||||||
|
Context.NOTIFICATION_SERVICE)).cancel(NOTIFICATION_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableNotification(Notification n) {
|
||||||
|
((NotificationManager) mContext.getSystemService(
|
||||||
|
Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PendingIntent prepareNotificationIntent() {
|
||||||
|
return PendingIntent.getActivity(mContext, 0,
|
||||||
|
new VpnManager(mContext).createSettingsActivityIntent(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getNotificationTitle(boolean connected) {
|
||||||
|
String connectedOrNot = connected
|
||||||
|
? mContext.getString(R.string.vpn_notification_connected)
|
||||||
|
: mContext.getString(
|
||||||
|
R.string.vpn_notification_disconnected);
|
||||||
|
return String.format(
|
||||||
|
mContext.getString(R.string.vpn_notification_title),
|
||||||
|
mProfile.getName(), connectedOrNot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTimeFormat(long duration) {
|
||||||
|
long hours = duration / 3600;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (hours > 0) sb.append(hours).append(':');
|
||||||
|
sb.append(String.format("%02d:%02d", (duration % 3600 / 60),
|
||||||
|
(duration % 60)));
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getNotificationMessage(boolean connected) {
|
||||||
|
if (connected) {
|
||||||
|
long time = (System.currentTimeMillis() - mStartTime) / 1000;
|
||||||
|
return String.format(mContext.getString(
|
||||||
|
R.string.vpn_notification_connected_message),
|
||||||
|
getTimeFormat(time));
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2007, 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.android.server.vpn;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.vpn.IVpnService;
|
||||||
|
import android.net.vpn.L2tpIpsecProfile;
|
||||||
|
import android.net.vpn.L2tpProfile;
|
||||||
|
import android.net.vpn.VpnManager;
|
||||||
|
import android.net.vpn.VpnProfile;
|
||||||
|
import android.net.vpn.VpnState;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The service class for managing a VPN connection. It implements the
|
||||||
|
* {@link IVpnService} binder interface.
|
||||||
|
*/
|
||||||
|
public class VpnServiceBinder extends Service {
|
||||||
|
private final String TAG = VpnServiceBinder.class.getSimpleName();
|
||||||
|
|
||||||
|
// The actual implementation is delegated to the VpnService class.
|
||||||
|
private VpnService<? extends VpnProfile> mService;
|
||||||
|
|
||||||
|
private final IBinder mBinder = new IVpnService.Stub() {
|
||||||
|
public boolean connect(VpnProfile p, String username, String password) {
|
||||||
|
return VpnServiceBinder.this.connect(p, username, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disconnect() {
|
||||||
|
if (mService != null) mService.onDisconnect(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkStatus(VpnProfile p) {
|
||||||
|
VpnServiceBinder.this.checkStatus(p);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return mBinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized boolean connect(
|
||||||
|
VpnProfile p, String username, String password) {
|
||||||
|
if (mService != null) return false;
|
||||||
|
try {
|
||||||
|
mService = createService(p);
|
||||||
|
mService.onConnect(username, password);
|
||||||
|
return true;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Log.e(TAG, "connect()", e);
|
||||||
|
if (mService != null) mService.onError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void checkStatus(VpnProfile p) {
|
||||||
|
if (mService == null) broadcastConnectivity(p.getName(), VpnState.IDLE);
|
||||||
|
|
||||||
|
if (!p.getName().equals(mService.mProfile.getName())) {
|
||||||
|
broadcastConnectivity(p.getName(), VpnState.IDLE);
|
||||||
|
} else {
|
||||||
|
broadcastConnectivity(p.getName(), mService.getState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private VpnService<? extends VpnProfile> createService(VpnProfile p) {
|
||||||
|
switch (p.getType()) {
|
||||||
|
case L2TP_IPSEC:
|
||||||
|
L2tpIpsecService l2tpIpsec = new L2tpIpsecService();
|
||||||
|
l2tpIpsec.setContext(this, (L2tpIpsecProfile) p);
|
||||||
|
return l2tpIpsec;
|
||||||
|
|
||||||
|
case L2TP:
|
||||||
|
L2tpService l2tp = new L2tpService();
|
||||||
|
l2tp.setContext(this, (L2tpProfile) p);
|
||||||
|
return l2tp;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void broadcastConnectivity(String name, VpnState s) {
|
||||||
|
new VpnManager(this).broadcastConnectivity(name, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,8 @@ import android.os.Parcel;
|
|||||||
* {@hide}
|
* {@hide}
|
||||||
*/
|
*/
|
||||||
public class L2tpIpsecProfile extends SingleServerProfile {
|
public class L2tpIpsecProfile extends SingleServerProfile {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private String mUserCertificate;
|
private String mUserCertificate;
|
||||||
private String mCaCertificate;
|
private String mCaCertificate;
|
||||||
private String mUserkey;
|
private String mUserkey;
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ package android.net.vpn;
|
|||||||
* {@hide}
|
* {@hide}
|
||||||
*/
|
*/
|
||||||
public class L2tpProfile extends SingleServerProfile {
|
public class L2tpProfile extends SingleServerProfile {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VpnType getType() {
|
public VpnType getType() {
|
||||||
return VpnType.L2TP;
|
return VpnType.L2TP;
|
||||||
|
|||||||
@@ -48,19 +48,8 @@ public class VpnManager {
|
|||||||
private static final String PACKAGE_PREFIX =
|
private static final String PACKAGE_PREFIX =
|
||||||
VpnManager.class.getPackage().getName() + ".";
|
VpnManager.class.getPackage().getName() + ".";
|
||||||
|
|
||||||
/** Action to start the activity of installing a new profile. */
|
// Action to start VPN service
|
||||||
public static final String ACTION_VPN_INSTALL_PROFILE =
|
private static final String ACTION_VPN_SERVICE = PACKAGE_PREFIX + "SERVICE";
|
||||||
PACKAGE_PREFIX + "INSTALL_PROFILE";
|
|
||||||
/**
|
|
||||||
* Key to the installation path in the intent of installing a new profile.
|
|
||||||
*/
|
|
||||||
public static final String KEY_INSTALLATION_PATH = "install_path";
|
|
||||||
public static final String DEFAULT_INSTALLATION_PATH =
|
|
||||||
"/data/local/tmp/vpn";
|
|
||||||
|
|
||||||
// Action to start VPN installation monitor service
|
|
||||||
private static final String SERVICE_VPN_INSTALL_MONITOR =
|
|
||||||
PACKAGE_PREFIX + "INSTALLATION_MONITOR";
|
|
||||||
|
|
||||||
// Action to start VPN settings
|
// Action to start VPN settings
|
||||||
private static final String ACTION_VPN_SETTINGS = PACKAGE_PREFIX + "SETTINGS";
|
private static final String ACTION_VPN_SETTINGS = PACKAGE_PREFIX + "SETTINGS";
|
||||||
@@ -112,53 +101,29 @@ public class VpnManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getServiceActionName(VpnType type) {
|
/**
|
||||||
return PACKAGE_PREFIX + type.getServiceName();
|
* Starts the VPN service to establish VPN connection.
|
||||||
|
*/
|
||||||
|
public void startVpnService() {
|
||||||
|
mContext.startService(new Intent(ACTION_VPN_SERVICE));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the VPN service of the specified type.
|
* Stops the VPN service.
|
||||||
*/
|
*/
|
||||||
public boolean startService(VpnType type) {
|
public void stopVpnService() {
|
||||||
String serviceAction = getServiceActionName(type);
|
mContext.stopService(new Intent(ACTION_VPN_SERVICE));
|
||||||
if (serviceAction != null) {
|
|
||||||
Log.i(TAG, "start service: " + serviceAction);
|
|
||||||
mContext.startService(new Intent(serviceAction));
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "unknown vpn type to start service for: " + type);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops the VPN service of the specified type.
|
* Binds the specified ServiceConnection with the VPN service.
|
||||||
*/
|
*/
|
||||||
public void stopService(VpnType type) {
|
public boolean bindVpnService(ServiceConnection c) {
|
||||||
String serviceAction = getServiceActionName(type);
|
if (!mContext.bindService(new Intent(ACTION_VPN_SERVICE), c, 0)) {
|
||||||
if (serviceAction != null) {
|
Log.w(TAG, "failed to connect to VPN service");
|
||||||
Log.i(TAG, "stop service for: " + type);
|
|
||||||
mContext.stopService(new Intent(serviceAction));
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "unknown vpn type to stop service for: " + type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Binds the specified ServiceConnection with the VPN service of the
|
|
||||||
* specified type.
|
|
||||||
*/
|
|
||||||
public boolean bindService(VpnType type, ServiceConnection c) {
|
|
||||||
String serviceAction = getServiceActionName(type);
|
|
||||||
if (serviceAction == null) {
|
|
||||||
Log.w(TAG, "unknown vpn type to bind service for: " + type);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!mContext.bindService(new Intent(serviceAction), c, 0)) {
|
|
||||||
Log.w(TAG, "failed to connect to service: " + type);
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
Log.v(TAG, "succeeded to connect to service: " + type);
|
Log.d(TAG, "succeeded to connect to VPN service");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,21 +146,6 @@ public class VpnManager {
|
|||||||
mContext.unregisterReceiver(r);
|
mContext.unregisterReceiver(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the installation monitor service.
|
|
||||||
* The service monitors the default installtion path (under /data/local/tmp)
|
|
||||||
* and automatically starts the activity to create a new profile when new
|
|
||||||
* configuration files appear in that path.
|
|
||||||
*/
|
|
||||||
public void startInstallationMonitorService() {
|
|
||||||
mContext.startService(new Intent(SERVICE_VPN_INSTALL_MONITOR));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Stops the installation monitor service. */
|
|
||||||
public void stopInstallationMonitorService() {
|
|
||||||
mContext.stopService(new Intent(SERVICE_VPN_INSTALL_MONITOR));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Starts the VPN settings activity. */
|
/** Starts the VPN settings activity. */
|
||||||
public void startSettingsActivity() {
|
public void startSettingsActivity() {
|
||||||
Intent intent = new Intent(ACTION_VPN_SETTINGS);
|
Intent intent = new Intent(ACTION_VPN_SETTINGS);
|
||||||
@@ -203,14 +153,10 @@ public class VpnManager {
|
|||||||
mContext.startActivity(intent);
|
mContext.startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Creates an intent to start the VPN settings activity. */
|
||||||
* Starts the activity to install a customized profile.
|
public Intent createSettingsActivityIntent() {
|
||||||
* @param installPath the path where all the configuration files are located
|
Intent intent = new Intent(ACTION_VPN_SETTINGS);
|
||||||
*/
|
|
||||||
public void startInstallProfileActivity(String installPath) {
|
|
||||||
Intent intent = new Intent(ACTION_VPN_INSTALL_PROFILE);
|
|
||||||
intent.putExtra(KEY_INSTALLATION_PATH, installPath);
|
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
mContext.startActivity(intent);
|
return intent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ import java.io.Serializable;
|
|||||||
* A VPN profile.
|
* A VPN profile.
|
||||||
* {@hide}
|
* {@hide}
|
||||||
*/
|
*/
|
||||||
public abstract class VpnProfile implements Parcelable {
|
public abstract class VpnProfile implements Parcelable, Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
private String mName; // unique display name
|
private String mName; // unique display name
|
||||||
private String mId; // unique identifier
|
private String mId; // unique identifier
|
||||||
private String mDomainSuffices; // space separated list
|
private String mDomainSuffices; // space separated list
|
||||||
|
|||||||
@@ -24,8 +24,10 @@ package android.net.vpn;
|
|||||||
* {@link CONNECTED} if successful; back to {@link IDLE} if failed.
|
* {@link CONNECTED} if successful; back to {@link IDLE} if failed.
|
||||||
* When the connection is about to be torn down, it goes to
|
* When the connection is about to be torn down, it goes to
|
||||||
* {@link DISCONNECTING} and then {@link IDLE}.
|
* {@link DISCONNECTING} and then {@link IDLE}.
|
||||||
|
* {@link CANCELLED} is a state when a VPN connection attempt is aborted, and
|
||||||
|
* is in transition to {@link IDLE}.
|
||||||
* {@hide}
|
* {@hide}
|
||||||
*/
|
*/
|
||||||
public enum VpnState {
|
public enum VpnState {
|
||||||
CONNECTING, DISCONNECTING, CONNECTED, IDLE
|
CONNECTING, DISCONNECTING, CANCELLED, CONNECTED, IDLE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,8 +39,4 @@ public enum VpnType {
|
|||||||
public Class<? extends VpnProfile> getProfileClass() {
|
public Class<? extends VpnProfile> getProfileClass() {
|
||||||
return mClass;
|
return mClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getServiceName() {
|
|
||||||
return this.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user