M7350v1_en_gpl

This commit is contained in:
T
2024-09-09 08:52:07 +00:00
commit f9cc65cfda
65988 changed files with 26357421 additions and 0 deletions

View File

@ -0,0 +1,199 @@
/*
* Copyright (C) 2009, 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.net.vpn.VpnManager;
import android.os.SystemProperties;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
/**
* Proxy to start, stop and interact with a VPN daemon.
* The daemon is expected to accept connection through Unix domain socket.
* When the proxy successfully starts the daemon, it will establish a socket
* connection with the daemon, to both send commands to the daemon and receive
* response and connecting error code from the daemon.
*/
class DaemonProxy implements Serializable {
private static final long serialVersionUID = 1L;
private static final boolean DBG = true;
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 mName;
private String mTag;
private transient LocalSocket mControlSocket;
/**
* Creates a proxy of the specified daemon.
* @param daemonName name of the daemon
*/
DaemonProxy(String daemonName) {
mName = daemonName;
mTag = "SProxy_" + daemonName;
}
String getName() {
return mName;
}
void start() throws IOException {
String svc = mName;
Log.i(mTag, "Start VPN daemon: " + svc);
SystemProperties.set(SVC_START_CMD, svc);
if (!blockUntil(SVC_STATE_RUNNING, WAITING_TIME)) {
throw new IOException("cannot start service: " + svc);
} else {
mControlSocket = createServiceSocket();
}
}
void sendCommand(String ...args) throws IOException {
OutputStream out = getControlSocketOutput();
for (String arg : args) outputString(out, arg);
out.write(END_OF_ARGUMENTS);
out.flush();
int result = getResultFromSocket(true);
if (result != args.length) {
throw new IOException("socket error, result from service: "
+ result);
}
}
// returns 0 if nothing is in the receive buffer
int getResultFromSocket() throws IOException {
return getResultFromSocket(false);
}
void closeControlSocket() {
if (mControlSocket == null) return;
try {
mControlSocket.close();
} catch (IOException e) {
Log.w(mTag, "close control socket", e);
} finally {
mControlSocket = null;
}
}
void stop() {
String svc = mName;
Log.i(mTag, "Stop VPN daemon: " + svc);
SystemProperties.set(SVC_STOP_CMD, svc);
boolean success = blockUntil(SVC_STATE_STOPPED, 5);
if (DBG) Log.d(mTag, "stopping " + svc + ", success? " + success);
}
boolean isStopped() {
String cmd = SVC_STATE_CMD_PREFIX + mName;
return SVC_STATE_STOPPED.equals(SystemProperties.get(cmd));
}
private int getResultFromSocket(boolean blocking) throws IOException {
LocalSocket s = mControlSocket;
if (s == null) return 0;
InputStream in = s.getInputStream();
if (!blocking && in.available() == 0) return 0;
int data = in.read();
Log.i(mTag, "got data from control socket: " + data);
return data;
}
private LocalSocket createServiceSocket() throws IOException {
LocalSocket s = new LocalSocket();
LocalSocketAddress a = new LocalSocketAddress(mName,
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) {
if (DBG) Log.d(mTag, "service not yet listen()ing; try again");
excp = e;
sleep(500);
}
}
throw excp;
}
private OutputStream getControlSocketOutput() throws IOException {
if (mControlSocket != null) {
return mControlSocket.getOutputStream();
} else {
throw new IOException("no control socket available");
}
}
/**
* 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 + mName;
int sleepTime = 200; // ms
int n = waitTime * 1000 / sleepTime;
for (int i = 0; i < n; i++) {
if (expectedState.equals(SystemProperties.get(cmd))) {
if (DBG) {
Log.d(mTag, mName + " is " + expectedState + " after "
+ (i * sleepTime) + " msec");
}
break;
}
sleep(sleepTime);
}
return expectedState.equals(SystemProperties.get(cmd));
}
private void outputString(OutputStream out, String s) throws IOException {
byte[] bytes = s.getBytes();
out.write(bytes.length);
out.write(bytes);
out.flush();
}
private void sleep(int msec) {
try {
Thread.currentThread().sleep(msec);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (C) 2009, 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.L2tpIpsecPskProfile;
import java.io.IOException;
/**
* The service that manages the preshared key based L2TP-over-IPSec VPN
* connection.
*/
class L2tpIpsecPskService extends VpnService<L2tpIpsecPskProfile> {
private static final String IPSEC = "racoon";
@Override
protected void connect(String serverIp, String username, String password)
throws IOException {
L2tpIpsecPskProfile p = getProfile();
VpnDaemons daemons = getDaemons();
// IPSEC
daemons.startIpsecForL2tp(serverIp, p.getPresharedKey())
.closeControlSocket();
sleep(2000); // 2 seconds
// L2TP
daemons.startL2tp(serverIp,
(p.isSecretEnabled() ? p.getSecretString() : null),
username, password);
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2009, 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.Credentials;
import java.io.IOException;
/**
* The service that manages the certificate based L2TP-over-IPSec VPN connection.
*/
class L2tpIpsecService extends VpnService<L2tpIpsecProfile> {
private static final String IPSEC = "racoon";
@Override
protected void connect(String serverIp, String username, String password)
throws IOException {
L2tpIpsecProfile p = getProfile();
VpnDaemons daemons = getDaemons();
// IPSEC
DaemonProxy ipsec = daemons.startIpsecForL2tp(serverIp,
Credentials.USER_PRIVATE_KEY + p.getUserCertificate(),
Credentials.USER_CERTIFICATE + p.getUserCertificate(),
Credentials.CA_CERTIFICATE + p.getCaCertificate());
ipsec.closeControlSocket();
sleep(2000); // 2 seconds
// L2TP
daemons.startL2tp(serverIp,
(p.isSecretEnabled() ? p.getSecretString() : null),
username, password);
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2009, 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> {
@Override
protected void connect(String serverIp, String username, String password)
throws IOException {
L2tpProfile p = getProfile();
getDaemons().startL2tp(serverIp,
(p.isSecretEnabled() ? p.getSecretString() : null),
username, password);
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) 2009, 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.PptpProfile;
import java.io.IOException;
/**
* The service that manages the PPTP VPN connection.
*/
class PptpService extends VpnService<PptpProfile> {
@Override
protected void connect(String serverIp, String username, String password)
throws IOException {
PptpProfile p = getProfile();
getDaemons().startPptp(serverIp, username, password,
p.isEncryptionEnabled());
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2009, 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 java.io.IOException;
/**
* Exception thrown when a connecting attempt fails.
*/
class VpnConnectingError extends IOException {
private int mErrorCode;
VpnConnectingError(int errorCode) {
super("Connecting error: " + errorCode);
mErrorCode = errorCode;
}
int getErrorCode() {
return mErrorCode;
}
}

View File

@ -0,0 +1,147 @@
/*
* Copyright (C) 2009, 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.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A helper class for managing native VPN daemons.
*/
class VpnDaemons implements Serializable {
static final long serialVersionUID = 1L;
private final String TAG = VpnDaemons.class.getSimpleName();
private static final String MTPD = "mtpd";
private static final String IPSEC = "racoon";
private static final String L2TP = "l2tp";
private static final String L2TP_PORT = "1701";
private static final String PPTP = "pptp";
private static final String PPTP_PORT = "1723";
private static final String VPN_LINKNAME = "vpn";
private static final String PPP_ARGS_SEPARATOR = "";
private List<DaemonProxy> mDaemonList = new ArrayList<DaemonProxy>();
public DaemonProxy startL2tp(String serverIp, String secret,
String username, String password) throws IOException {
return startMtpd(L2TP, serverIp, L2TP_PORT, secret, username, password,
false);
}
public DaemonProxy startPptp(String serverIp, String username,
String password, boolean encryption) throws IOException {
return startMtpd(PPTP, serverIp, PPTP_PORT, null, username, password,
encryption);
}
public DaemonProxy startIpsecForL2tp(String serverIp, String pskKey)
throws IOException {
DaemonProxy ipsec = startDaemon(IPSEC);
ipsec.sendCommand(serverIp, L2TP_PORT, pskKey);
return ipsec;
}
public DaemonProxy startIpsecForL2tp(String serverIp, String userKeyKey,
String userCertKey, String caCertKey) throws IOException {
DaemonProxy ipsec = startDaemon(IPSEC);
ipsec.sendCommand(serverIp, L2TP_PORT, userKeyKey, userCertKey,
caCertKey);
return ipsec;
}
public synchronized void stopAll() {
new DaemonProxy(MTPD).stop();
new DaemonProxy(IPSEC).stop();
}
public synchronized void closeSockets() {
for (DaemonProxy s : mDaemonList) s.closeControlSocket();
}
public synchronized boolean anyDaemonStopped() {
for (DaemonProxy s : mDaemonList) {
if (s.isStopped()) {
Log.w(TAG, " VPN daemon gone: " + s.getName());
return true;
}
}
return false;
}
public synchronized int getSocketError() {
for (DaemonProxy s : mDaemonList) {
int errCode = getResultFromSocket(s);
if (errCode != 0) return errCode;
}
return 0;
}
private synchronized DaemonProxy startDaemon(String daemonName)
throws IOException {
DaemonProxy daemon = new DaemonProxy(daemonName);
mDaemonList.add(daemon);
daemon.start();
return daemon;
}
private int getResultFromSocket(DaemonProxy s) {
try {
return s.getResultFromSocket();
} catch (IOException e) {
return -1;
}
}
private DaemonProxy startMtpd(String protocol,
String serverIp, String port, String secret, String username,
String password, boolean encryption) throws IOException {
ArrayList<String> args = new ArrayList<String>();
args.addAll(Arrays.asList(protocol, serverIp, port));
if (secret != null) args.add(secret);
args.add(PPP_ARGS_SEPARATOR);
addPppArguments(args, serverIp, username, password, encryption);
DaemonProxy mtpd = startDaemon(MTPD);
mtpd.sendCommand(args.toArray(new String[args.size()]));
return mtpd;
}
private static void addPppArguments(ArrayList<String> args, String serverIp,
String username, String password, boolean encryption)
throws IOException {
args.addAll(Arrays.asList(
"linkname", VPN_LINKNAME,
"name", username,
"password", password,
"refuse-eap", "nodefaultroute", "usepeerdns",
"idle", "1800",
"mtu", "1400",
"mru", "1400"));
if (encryption) {
args.add("+mppe");
}
}
}

View File

@ -0,0 +1,486 @@
/*
* Copyright (C) 2009, 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.VpnManager;
import android.net.vpn.VpnProfile;
import android.net.vpn.VpnState;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.Log;
import java.io.IOException;
import java.io.Serializable;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
/**
* The service base class for managing a type of VPN connection.
*/
abstract class VpnService<E extends VpnProfile> implements Serializable {
static final long serialVersionUID = 1L;
private static final boolean DBG = true;
private static final int NOTIFICATION_ID = 1;
private static final String DNS1 = "net.dns1";
private static final String DNS2 = "net.dns2";
private static final String VPN_DNS1 = "vpn.dns1";
private static final String VPN_DNS2 = "vpn.dns2";
private static final String VPN_STATUS = "vpn.status";
private static final String VPN_IS_UP = "ok";
private static final String VPN_IS_DOWN = "down";
private static final String REMOTE_IP = "net.ipremote";
private static final String DNS_DOMAIN_SUFFICES = "net.dns.search";
private final String TAG = VpnService.class.getSimpleName();
// FIXME: profile is only needed in connecting phase, so we can just save
// the profile name and service class name for recovery
E mProfile;
transient VpnServiceBinder mContext;
private VpnState mState = VpnState.IDLE;
private Throwable mError;
// connection settings
private String mOriginalDns1;
private String mOriginalDns2;
private String mOriginalDomainSuffices;
private String mLocalIp;
private String mLocalIf;
private long mStartTime; // VPN connection start time
// for helping managing daemons
private VpnDaemons mDaemons = new VpnDaemons();
// for helping showing, updating notification
private transient NotificationHelper mNotification;
/**
* Establishes a VPN connection with the specified username and password.
*/
protected abstract void connect(String serverIp, String username,
String password) throws IOException;
/**
* Returns the daemons management class for this service object.
*/
protected VpnDaemons getDaemons() {
return mDaemons;
}
/**
* Returns the VPN profile associated with the connection.
*/
protected E getProfile() {
return mProfile;
}
/**
* Returns the IP address of the specified host name.
*/
protected String getIp(String hostName) throws IOException {
return InetAddress.getByName(hostName).getHostAddress();
}
void setContext(VpnServiceBinder context, E profile) {
mProfile = profile;
recover(context);
}
void recover(VpnServiceBinder context) {
mContext = context;
mNotification = new NotificationHelper();
if (VpnState.CONNECTED.equals(mState)) {
Log.i("VpnService", " recovered: " + mProfile.getName());
startConnectivityMonitor();
}
}
VpnState getState() {
return mState;
}
synchronized boolean onConnect(String username, String password) {
try {
setState(VpnState.CONNECTING);
mDaemons.stopAll();
String serverIp = getIp(getProfile().getServerName());
saveLocalIpAndInterface(serverIp);
onBeforeConnect();
connect(serverIp, username, password);
waitUntilConnectedOrTimedout();
return true;
} catch (Throwable e) {
onError(e);
return false;
}
}
synchronized void onDisconnect() {
try {
Log.i(TAG, "disconnecting VPN...");
setState(VpnState.DISCONNECTING);
mNotification.showDisconnect();
mDaemons.stopAll();
} catch (Throwable e) {
Log.e(TAG, "onDisconnect()", e);
} finally {
onFinalCleanUp();
}
}
private void onError(Throwable error) {
// error may occur during or after connection setup
// and it may be due to one or all services gone
if (mError != null) {
Log.w(TAG, " multiple errors occur, record the last one: "
+ error);
}
Log.e(TAG, "onError()", error);
mError = error;
onDisconnect();
}
private void onError(int errorCode) {
onError(new VpnConnectingError(errorCode));
}
private void onBeforeConnect() throws IOException {
mNotification.disableNotification();
SystemProperties.set(VPN_DNS1, "");
SystemProperties.set(VPN_DNS2, "");
SystemProperties.set(VPN_STATUS, VPN_IS_DOWN);
if (DBG) {
Log.d(TAG, " VPN UP: " + SystemProperties.get(VPN_STATUS));
}
}
private void waitUntilConnectedOrTimedout() throws IOException {
sleep(2000); // 2 seconds
for (int i = 0; i < 80; i++) {
if (mState != VpnState.CONNECTING) {
break;
} else if (VPN_IS_UP.equals(
SystemProperties.get(VPN_STATUS))) {
onConnected();
return;
} else {
int err = mDaemons.getSocketError();
if (err != 0) {
onError(err);
return;
}
}
sleep(500); // 0.5 second
}
if (mState == VpnState.CONNECTING) {
onError(new IOException("Connecting timed out"));
}
}
private synchronized void onConnected() throws IOException {
if (DBG) Log.d(TAG, "onConnected()");
mDaemons.closeSockets();
saveOriginalDns();
saveAndSetDomainSuffices();
mStartTime = System.currentTimeMillis();
// Correct order to make sure VpnService doesn't break when killed:
// (1) set state to CONNECTED
// (2) save states
// (3) set DNS
setState(VpnState.CONNECTED);
saveSelf();
setVpnDns();
startConnectivityMonitor();
}
private void saveSelf() throws IOException {
mContext.saveStates();
}
private synchronized void onFinalCleanUp() {
if (DBG) Log.d(TAG, "onFinalCleanUp()");
if (mState == VpnState.IDLE) return;
// keep the notification when error occurs
if (!anyError()) mNotification.disableNotification();
restoreOriginalDns();
restoreOriginalDomainSuffices();
setState(VpnState.IDLE);
// stop the service itself
SystemProperties.set(VPN_STATUS, VPN_IS_DOWN);
mContext.removeStates();
mContext.stopSelf();
}
private boolean anyError() {
return (mError != null);
}
private void restoreOriginalDns() {
// restore only if they are not overridden
String vpnDns1 = SystemProperties.get(VPN_DNS1);
if (vpnDns1.equals(SystemProperties.get(DNS1))) {
Log.i(TAG, String.format("restore original dns prop: %s --> %s",
SystemProperties.get(DNS1), mOriginalDns1));
Log.i(TAG, String.format("restore original dns prop: %s --> %s",
SystemProperties.get(DNS2), mOriginalDns2));
SystemProperties.set(DNS1, mOriginalDns1);
SystemProperties.set(DNS2, mOriginalDns2);
}
}
private void saveOriginalDns() {
mOriginalDns1 = SystemProperties.get(DNS1);
mOriginalDns2 = SystemProperties.get(DNS2);
Log.i(TAG, String.format("save original dns prop: %s, %s",
mOriginalDns1, mOriginalDns2));
}
private void setVpnDns() {
String vpnDns1 = SystemProperties.get(VPN_DNS1);
String vpnDns2 = SystemProperties.get(VPN_DNS2);
SystemProperties.set(DNS1, vpnDns1);
SystemProperties.set(DNS2, vpnDns2);
Log.i(TAG, String.format("set vpn dns prop: %s, %s",
vpnDns1, vpnDns2));
}
private void saveAndSetDomainSuffices() {
mOriginalDomainSuffices = SystemProperties.get(DNS_DOMAIN_SUFFICES);
Log.i(TAG, "save original suffices: " + mOriginalDomainSuffices);
String list = mProfile.getDomainSuffices();
if (!TextUtils.isEmpty(list)) {
SystemProperties.set(DNS_DOMAIN_SUFFICES, list);
}
}
private void restoreOriginalDomainSuffices() {
Log.i(TAG, "restore original suffices --> " + mOriginalDomainSuffices);
SystemProperties.set(DNS_DOMAIN_SUFFICES, mOriginalDomainSuffices);
}
private void setState(VpnState newState) {
mState = newState;
broadcastConnectivity(newState);
}
private void broadcastConnectivity(VpnState s) {
VpnManager m = new VpnManager(mContext);
Throwable err = mError;
if ((s == VpnState.IDLE) && (err != null)) {
if (err instanceof UnknownHostException) {
m.broadcastConnectivity(mProfile.getName(), s,
VpnManager.VPN_ERROR_UNKNOWN_SERVER);
} else if (err instanceof VpnConnectingError) {
m.broadcastConnectivity(mProfile.getName(), s,
((VpnConnectingError) err).getErrorCode());
} else if (VPN_IS_UP.equals(SystemProperties.get(VPN_STATUS))) {
m.broadcastConnectivity(mProfile.getName(), s,
VpnManager.VPN_ERROR_CONNECTION_LOST);
} else {
m.broadcastConnectivity(mProfile.getName(), s,
VpnManager.VPN_ERROR_CONNECTION_FAILED);
}
} else {
m.broadcastConnectivity(mProfile.getName(), s);
}
}
private void startConnectivityMonitor() {
new Thread(new Runnable() {
public void run() {
Log.i(TAG, "VPN connectivity monitor running");
try {
for (int i = 10; ; i--) {
long now = System.currentTimeMillis();
boolean heavyCheck = i == 0;
synchronized (VpnService.this) {
if (mState != VpnState.CONNECTED) break;
mNotification.update(now);
if (heavyCheck) {
i = 10;
if (checkConnectivity()) checkDns();
}
long t = 1000L - System.currentTimeMillis() + now;
if (t > 100L) VpnService.this.wait(t);
}
}
} catch (InterruptedException e) {
onError(e);
}
Log.i(TAG, "VPN connectivity monitor stopped");
}
}).start();
}
private void saveLocalIpAndInterface(String serverIp) throws IOException {
DatagramSocket s = new DatagramSocket();
int port = 80; // arbitrary
s.connect(InetAddress.getByName(serverIp), port);
InetAddress localIp = s.getLocalAddress();
mLocalIp = localIp.getHostAddress();
NetworkInterface localIf = NetworkInterface.getByInetAddress(localIp);
mLocalIf = (localIf == null) ? null : localIf.getName();
if (TextUtils.isEmpty(mLocalIf)) {
throw new IOException("Local interface is empty!");
}
if (DBG) {
Log.d(TAG, " Local IP: " + mLocalIp + ", if: " + mLocalIf);
}
}
// returns false if vpn connectivity is broken
private boolean checkConnectivity() {
if (mDaemons.anyDaemonStopped() || isLocalIpChanged()) {
onError(new IOException("Connectivity lost"));
return false;
} else {
return true;
}
}
private void checkDns() {
String dns1 = SystemProperties.get(DNS1);
String vpnDns1 = SystemProperties.get(VPN_DNS1);
if (!dns1.equals(vpnDns1) && dns1.equals(mOriginalDns1)) {
// dhcp expires?
setVpnDns();
}
}
private boolean isLocalIpChanged() {
try {
InetAddress localIp = InetAddress.getByName(mLocalIp);
NetworkInterface localIf =
NetworkInterface.getByInetAddress(localIp);
if (localIf == null || !mLocalIf.equals(localIf.getName())) {
Log.w(TAG, " local If changed from " + mLocalIf
+ " to " + localIf);
return true;
} else {
return false;
}
} catch (IOException e) {
Log.w(TAG, "isLocalIpChanged()", e);
return true;
}
}
protected void sleep(int ms) {
try {
Thread.currentThread().sleep(ms);
} catch (InterruptedException e) {
}
}
private class DaemonHelper implements Serializable {
}
// Helper class for showing, updating notification.
private class NotificationHelper {
void update(long now) {
String title = getNotificationTitle(true);
Notification n = new Notification(R.drawable.vpn_connected, title,
mStartTime);
n.setLatestEventInfo(mContext, title,
getConnectedNotificationMessage(now),
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,
getDisconnectedNotificationMessage(),
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 formatString = connected
? mContext.getString(
R.string.vpn_notification_title_connected)
: mContext.getString(
R.string.vpn_notification_title_disconnected);
return String.format(formatString, mProfile.getName());
}
private String getFormattedTime(int duration) {
int 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 getConnectedNotificationMessage(long now) {
return getFormattedTime((int) (now - mStartTime) / 1000);
}
private String getDisconnectedNotificationMessage() {
return mContext.getString(
R.string.vpn_notification_hint_disconnected);
}
}
}

View File

@ -0,0 +1,188 @@
/*
* Copyright (C) 2009, 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.L2tpIpsecPskProfile;
import android.net.vpn.L2tpProfile;
import android.net.vpn.PptpProfile;
import android.net.vpn.VpnManager;
import android.net.vpn.VpnProfile;
import android.net.vpn.VpnState;
import android.os.Environment;
import android.os.IBinder;
import android.os.SystemProperties;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* The service class for managing a VPN connection. It implements the
* {@link IVpnService} binder interface.
*/
public class VpnServiceBinder extends Service {
private static final String TAG = VpnServiceBinder.class.getSimpleName();
private static final boolean DBG = true;
private static final String STATES_FILE_RELATIVE_PATH = "/misc/vpn/.states";
// The actual implementation is delegated to the VpnService class.
private VpnService<? extends VpnProfile> mService;
// TODO(oam): Test VPN when EFS is enabled (will do later)...
private static String getStateFilePath() {
// This call will return the correcu directory whether Encrypted FS is enabled or not
// Disabled: /data/misc/vpn/.states Enabled: /data/secure/misc/vpn/.states
return Environment.getSecureDataDirectory().getPath() + STATES_FILE_RELATIVE_PATH;
}
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() {
VpnServiceBinder.this.disconnect();
}
public void checkStatus(VpnProfile p) {
VpnServiceBinder.this.checkStatus(p);
}
};
@Override
public void onCreate() {
super.onCreate();
checkSavedStates();
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
void saveStates() throws IOException {
if (DBG) Log.d("VpnServiceBinder", " saving states");
ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream(getStateFilePath()));
oos.writeObject(mService);
oos.close();
}
void removeStates() {
try {
File f = new File(getStateFilePath());
if (f.exists()) f.delete();
} catch (Throwable e) {
if (DBG) Log.d("VpnServiceBinder", " remove states: " + e);
}
}
private synchronized boolean connect(final VpnProfile p,
final String username, final String password) {
if (mService != null) return false;
final VpnService s = mService = createService(p);
new Thread(new Runnable() {
public void run() {
s.onConnect(username, password);
}
}).start();
return true;
}
private synchronized void disconnect() {
if (mService == null) return;
final VpnService s = mService;
new Thread(new Runnable() {
public void run() {
s.onDisconnect();
}
}).start();
}
private synchronized void checkStatus(VpnProfile p) {
if ((mService == null)
|| (!p.getName().equals(mService.mProfile.getName()))) {
broadcastConnectivity(p.getName(), VpnState.IDLE);
} else {
broadcastConnectivity(p.getName(), mService.getState());
}
}
private void checkSavedStates() {
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
getStateFilePath()));
mService = (VpnService<? extends VpnProfile>) ois.readObject();
mService.recover(this);
ois.close();
} catch (FileNotFoundException e) {
// do nothing
} catch (Throwable e) {
Log.i("VpnServiceBinder", "recovery error, remove states: " + e);
removeStates();
}
}
private VpnService<? extends VpnProfile> createService(VpnProfile p) {
switch (p.getType()) {
case L2TP:
L2tpService l2tp = new L2tpService();
l2tp.setContext(this, (L2tpProfile) p);
return l2tp;
case PPTP:
PptpService pptp = new PptpService();
pptp.setContext(this, (PptpProfile) p);
return pptp;
case L2TP_IPSEC_PSK:
L2tpIpsecPskService psk = new L2tpIpsecPskService();
psk.setContext(this, (L2tpIpsecPskProfile) p);
return psk;
case L2TP_IPSEC:
L2tpIpsecService l2tpIpsec = new L2tpIpsecService();
l2tpIpsec.setContext(this, (L2tpIpsecProfile) p);
return l2tpIpsec;
default:
return null;
}
}
private void broadcastConnectivity(String name, VpnState s) {
new VpnManager(this).broadcastConnectivity(name, s);
}
}