summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/VpnServices/src/com/android/server/vpn/OpenvpnService.java386
-rw-r--r--packages/VpnServices/src/com/android/server/vpn/VpnService.java46
-rw-r--r--packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java6
-rw-r--r--vpn/java/android/net/vpn/OpenvpnProfile.java174
-rw-r--r--vpn/java/android/net/vpn/VpnType.java3
5 files changed, 608 insertions, 7 deletions
diff --git a/packages/VpnServices/src/com/android/server/vpn/OpenvpnService.java b/packages/VpnServices/src/com/android/server/vpn/OpenvpnService.java
new file mode 100644
index 0000000..5143199
--- /dev/null
+++ b/packages/VpnServices/src/com/android/server/vpn/OpenvpnService.java
@@ -0,0 +1,386 @@
+/*
+ * 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.OpenvpnProfile;
+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.util.ArrayList;
+
+/**
+ * The service that manages the openvpn VPN connection.
+ */
+class OpenvpnService extends VpnService<OpenvpnProfile> {
+ private static final String OPENVPN_DAEMON = "openvpn";
+ private static final String MTPD = "mtpd";
+ private static final String USE_INLINE = "[[INLINE]]";
+ private static final String USE_KEYSTORE = "[[ANDROID]]";
+ private static final String TAG = OpenvpnService.class.getSimpleName();
+ private static int count = 0;
+
+ private final String socketName = OPENVPN_DAEMON + getCount();
+
+ private transient OpenvpnThread thread = null;
+
+ private transient String mPassword;
+ private transient String mUsername;
+
+ private synchronized static String getCount() {
+ return Integer.toString(count++);
+ }
+
+ @Override
+ protected void connect(String serverIp, String username, String password)
+ throws IOException {
+ OpenvpnProfile p = getProfile();
+ ArrayList<String> args = new ArrayList<String>();
+
+ mUsername = username;
+ mPassword = password;
+
+ args.add(OPENVPN_DAEMON);
+ args.add("--dev"); args.add("tun");
+ args.add("--remote"); args.add(serverIp);
+ args.add("--nobind");
+ args.add("--proto"); args.add(p.getProto());
+ args.add("--client");
+ args.add("--rport"); args.add(p.getPort());
+ args.add("--ca"); args.add(USE_INLINE); args.add(USE_KEYSTORE + p.getCAFile());
+ args.add("--cert"); args.add(USE_INLINE); args.add(USE_KEYSTORE + p.getCertFile());
+ args.add("--key"); args.add(USE_INLINE); args.add(USE_KEYSTORE + p.getKeyFile());
+ args.add("--persist-tun");
+ args.add("--persist-key");
+ args.add("--management"); args.add("/dev/socket/" + socketName); args.add("unix");
+ args.add("--management-hold");
+ if (p.getUseCompLzo()) {
+ args.add("--comp-lzo");
+ }
+ if (p.getUserAuth()) {
+ args.add("--auth-user-pass");
+ args.add("--management-query-passwords");
+ }
+ if (p.getSupplyAddr()) {
+ args.add("--ifconfig"); args.add(p.getLocalAddr()); args.add(p.getRemoteAddr());
+ }
+
+ DaemonProxy mtpd = startDaemon(MTPD);
+ mtpd.sendCommand(args.toArray(new String[args.size()]));
+ }
+
+ @Override
+ protected void disconnect() {
+ if (thread != null)
+ thread.disconnectAndWait();
+ }
+
+ @Override
+ void waitUntilConnectedOrTimedout() throws IOException {
+ thread = new OpenvpnThread();
+ thread.openvpnStart();
+ thread.waitConnect(60);
+ setVpnStateUp(true);
+ }
+
+ @Override
+ protected void stopPreviouslyRunDaemons() {
+ stopDaemon(MtpdHelper.MTPD);
+ }
+
+ @Override
+ protected void recover() {
+ try {
+ thread = new OpenvpnThread();
+ thread.openvpnStart();
+ } catch (IOException e) {
+ onError(e);
+ }
+ }
+
+ void startConnectivityMonitor() {
+ /* Openvpn is completely event driven, so we don't need
+ * a polling monitor at all, so do nothing here */
+ }
+
+ private class OpenvpnThread extends Thread {
+ InputStream in;
+ OutputStream out;
+ LocalSocket mSocket;
+
+ boolean finalDisconnect = false;
+ boolean firstConnect = false;
+ boolean disconnecting = false;
+ boolean passwordError = false;
+
+ boolean SFbool;
+ volatile String SFreason;
+ String vpnState = "WAIT"; // initial state
+
+ OpenvpnThread() throws IOException {
+ openSocket();
+ in = mSocket.getInputStream();
+ out = mSocket.getOutputStream();
+ }
+
+ public void openvpnStart() throws IOException {
+ super.start();
+ send("state on"); // make state dynamic
+ send("log on"); // dynamically log over the socket
+ send("hold off"); // don't hold for subsequent reconnects
+ send("hold release"); // release from hold
+ send("bytecount 2"); // need this to update the monitor
+ }
+
+ public synchronized void disconnectAndWait() {
+ try {
+ disconnecting = true;
+ send("signal SIGTERM");
+ while (!finalDisconnect)
+ this.wait();
+ } catch(Exception e) {
+ // we're done
+ }
+ }
+
+ public synchronized void waitConnect(long seconds) throws IOException {
+ long endTime = System.currentTimeMillis() + seconds * 1000;
+ long wait;
+ while (!isConnected() && (wait = (endTime - System.currentTimeMillis())) > 0) {
+ try {
+ this.wait(wait);
+ } catch(InterruptedException e) {
+ // do nothing
+ }
+ if (passwordError)
+ throw new VpnConnectingError(VpnManager.VPN_ERROR_AUTH);
+ }
+ if (!isConnected())
+ throw new VpnConnectingError(VpnManager.VPN_ERROR_CONNECTION_FAILED);
+ firstConnect = true;
+ }
+
+ private boolean isConnected() {
+ return vpnState.equals("CONNECTED");
+ }
+
+ private void openSocket() throws IOException {
+ LocalSocket s = new LocalSocket();
+ LocalSocketAddress a = new LocalSocketAddress(socketName,
+ LocalSocketAddress.Namespace.RESERVED);
+ IOException excp = null;
+ for (int i = 0; i < 10; i++) {
+ try {
+ s.connect(a);
+ mSocket = s;
+ return;
+ } catch (IOException e) {
+ excp = e;
+ try {
+ Thread.currentThread().sleep(500);
+ } catch (InterruptedException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+ throw excp;
+ }
+
+ private synchronized boolean waitForSuccessOrFail() {
+ SFreason = null;
+ try {
+ while (SFreason == null) {
+ this.wait();
+ }
+ return SFbool;
+ } catch(InterruptedException e) {
+ return false;
+ }
+ }
+
+ private synchronized void signalSuccessOrFail(boolean success, String reason) {
+ SFbool = success;
+ SFreason = reason;
+ this.notifyAll();
+ }
+
+
+ private synchronized void sendAsync(String str) throws IOException {
+ str += "\n";
+ out.write(str.getBytes());
+ out.flush();
+ }
+
+ private boolean send(String str) throws IOException {
+ sendAsync(str);
+ return waitForSuccessOrFail();
+ }
+
+ private synchronized void signalState(String s) {
+ // state strings come as <date in secs>, <state>, <other stuff>
+ int first = s.indexOf(',');
+ if (first == -1)
+ return;
+ int second = s.indexOf(',', first + 1);
+ if (second == -1)
+ return;
+ String state = s.substring(first + 1, second);
+
+ /*
+ * state can be:
+ *
+ *
+ * CONNECTING -- OpenVPN's initial state.
+ * WAIT -- (Client only) Waiting for initial response
+ * from server.
+ * AUTH -- (Client only) Authenticating with server.
+ * GET_CONFIG -- (Client only) Downloading configuration options
+ * from server.
+ * ASSIGN_IP -- Assigning IP address to virtual network
+ * interface.
+ * ADD_ROUTES -- Adding routes to system.
+ * CONNECTED -- Initialization Sequence Completed.
+ * RECONNECTING -- A restart has occurred.
+ * EXITING -- A graceful exit is in progress.
+ *
+ * Really all we care about is connected or not
+ */
+ vpnState = state;
+ this.notifyAll();
+ if (state.equals("EXITING") && firstConnect && !disconnecting)
+ onError(new IOException("Connection Closed"));
+ }
+
+ private synchronized void signalPassword(String s) throws IOException {
+ /* message should be Need '<auth type>' password
+ * but coult be Verification Failed: '<auth type'
+ */
+
+ int first = s.indexOf('\'');
+ int second = s.indexOf('\'', first + 1);
+ final String authType = s.substring(first + 1, second);
+
+ /* AuthType can be one of
+ *
+ * "Auth" - regular client server authentication
+ * "Private Key" - password for private key (unimplemented)
+ */
+
+ if (s.startsWith("Need")) {
+ /* we're in the processor thread, so we have to send
+ * these asynchronously to avoid a deadlock */
+ sendAsync("username '" + authType +"' '" + mUsername + "'");
+ sendAsync("password '" + authType +"' '" + mPassword + "'");
+ } else {
+ // must be signalling authentication failure
+ passwordError = true;
+ this.notifyAll();
+ }
+ }
+
+ private void signalBytecount(String s) {
+ int index = s.indexOf(',');
+ if (index == -1)
+ // no , in message, ignore it
+ return;
+
+ String in = s.substring(0, index);
+ String out = s.substring(index+1);
+ vpnStateUpdate(Long.parseLong(in), Long.parseLong(out));
+ }
+
+ private void signalLog(String s) {
+ //log format is <date in secs>,<severity>,<message>
+ int first = s.indexOf(',');
+ if (first == -1)
+ return;
+ int second = s.indexOf(',', first + 1);
+ if (second == -1)
+ return;
+ String message = s.substring(second + 1);
+ Log.i("openvpn", message);
+ }
+
+ private void parseLine(String s) throws IOException {
+ int index = s.indexOf(':');
+ if (index == -1)
+ // no : in message, ignore it
+ return;
+
+ String token = s.substring(0, index);
+ String body = s.substring(index +1);
+
+ if (token.equals(">INFO")) {
+ // This is the starting string, just skip it
+ } else if (token.equals("SUCCESS")) {
+ signalSuccessOrFail(true, body);
+ } else if (token.equals("ERROR")) {
+ signalSuccessOrFail(false, body);
+ } else if (token.equals(">STATE")) {
+ signalState(body);
+ } else if (token.equals(">FATAL")) {
+ signalState("EXITING," + body);
+ } else if (token.equals(">PASSWORD")) {
+ signalPassword(body);
+ } else if (token.equals(">LOG")) {
+ signalLog(body);
+ }else if (token.equals(">HOLD")) {
+ // just warning us we're in a hold state, ignore
+ } else if (token.equals(">BYTECOUNT")) {
+ signalBytecount(body);
+ } else {
+ Log.w(TAG, "Unknown control token:\"" + token + "\"");
+ }
+ }
+
+ public void run() {
+
+ System.out.println("THREAD " + this + " RUNNING");
+
+ try {
+ int c;
+ StringBuffer s = new StringBuffer();
+ while (true) {
+ c = in.read();
+ if (c == -1)
+ throw new IOException("End of Stream");
+ if (c == '\n') {
+ parseLine(s.toString());
+ s = new StringBuffer();
+ continue;
+ }
+ if (c == '\r')
+ continue;
+ s.append((char)c);
+ }
+ } catch(IOException e) {
+ // terminate
+ } finally {
+ synchronized(this) {
+ finalDisconnect = true;
+ this.notifyAll();
+ }
+ System.out.println("THREAD " + this + " TERMINATED");
+ }
+ }
+ }
+}
diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnService.java b/packages/VpnServices/src/com/android/server/vpn/VpnService.java
index 63b87b1..6953a32 100644
--- a/packages/VpnServices/src/com/android/server/vpn/VpnService.java
+++ b/packages/VpnServices/src/com/android/server/vpn/VpnService.java
@@ -85,6 +85,13 @@ abstract class VpnService<E extends VpnProfile> implements Serializable {
String password) throws IOException;
/**
+ * Disconnects the vpn
+ */
+ protected void disconnect() {
+ mDaemons.stopAll();
+ }
+
+ /**
* Returns the daemons management class for this service object.
*/
protected VpnDaemons getDaemons() {
@@ -110,14 +117,19 @@ abstract class VpnService<E extends VpnProfile> implements Serializable {
recover(context);
}
+ // intended for override by subclasses
+ protected void recover() {
+ startConnectivityMonitor();
+ }
+
void recover(VpnServiceBinder context) {
mContext = context;
mNotification = new NotificationHelper();
if (VpnState.CONNECTED.equals(mState)) {
Log.i("VpnService", " recovered: " + mProfile.getName());
- startConnectivityMonitor();
- }
+ recover();
+ }
}
VpnState getState() {
@@ -147,7 +159,7 @@ abstract class VpnService<E extends VpnProfile> implements Serializable {
setState(VpnState.DISCONNECTING);
mNotification.showDisconnect();
- mDaemons.stopAll();
+ disconnect();
} catch (Throwable e) {
Log.e(TAG, "onDisconnect()", e);
} finally {
@@ -155,7 +167,7 @@ abstract class VpnService<E extends VpnProfile> implements Serializable {
}
}
- private void onError(Throwable error) {
+ 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) {
@@ -167,7 +179,7 @@ abstract class VpnService<E extends VpnProfile> implements Serializable {
onDisconnect();
}
- private void onError(int errorCode) {
+ void onError(int errorCode) {
onError(new VpnConnectingError(errorCode));
}
@@ -183,7 +195,7 @@ abstract class VpnService<E extends VpnProfile> implements Serializable {
}
}
- private void waitUntilConnectedOrTimedout() throws IOException {
+ void waitUntilConnectedOrTimedout() throws IOException {
sleep(2000); // 2 seconds
for (int i = 0; i < 80; i++) {
if (mState != VpnState.CONNECTING) {
@@ -207,6 +219,21 @@ abstract class VpnService<E extends VpnProfile> implements Serializable {
}
}
+ void setVpnStateUp(boolean state) throws IOException {
+ if (state) {
+ SystemProperties.set(VPN_STATUS, VPN_IS_UP);
+ onConnected();
+ mNotification.update();
+ } else {
+ SystemProperties.set(VPN_STATUS, VPN_IS_DOWN);
+ }
+ }
+
+ void vpnStateUpdate(long in, long out) {
+ // currently don't show in and out bytes in status
+ mNotification.update();
+ }
+
private synchronized void onConnected() throws IOException {
if (DBG) Log.d(TAG, "onConnected()");
@@ -276,6 +303,11 @@ abstract class VpnService<E extends VpnProfile> implements Serializable {
private void setVpnDns() {
String vpnDns1 = SystemProperties.get(VPN_DNS1);
String vpnDns2 = SystemProperties.get(VPN_DNS2);
+ if (vpnDns1.length() == 0) {
+ Log.i(TAG, "No vpn dns supplied, not updating");
+ return;
+ }
+
SystemProperties.set(DNS1, vpnDns1);
SystemProperties.set(DNS2, vpnDns2);
Log.i(TAG, String.format("set vpn dns prop: %s, %s",
@@ -323,7 +355,7 @@ abstract class VpnService<E extends VpnProfile> implements Serializable {
}
}
- private void startConnectivityMonitor() {
+ void startConnectivityMonitor() {
new Thread(new Runnable() {
public void run() {
Log.i(TAG, "VPN connectivity monitor running");
diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java b/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java
index 5672a01..64abf87 100644
--- a/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java
+++ b/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java
@@ -22,6 +22,7 @@ import android.net.vpn.IVpnService;
import android.net.vpn.L2tpIpsecProfile;
import android.net.vpn.L2tpIpsecPskProfile;
import android.net.vpn.L2tpProfile;
+import android.net.vpn.OpenvpnProfile;
import android.net.vpn.PptpProfile;
import android.net.vpn.VpnManager;
import android.net.vpn.VpnProfile;
@@ -159,6 +160,11 @@ public class VpnServiceBinder extends Service {
l2tp.setContext(this, (L2tpProfile) p);
return l2tp;
+ case OPENVPN:
+ OpenvpnService ovpn = new OpenvpnService();
+ ovpn.setContext(this, (OpenvpnProfile)p );
+ return ovpn;
+
case PPTP:
PptpService pptp = new PptpService();
pptp.setContext(this, (PptpProfile) p);
diff --git a/vpn/java/android/net/vpn/OpenvpnProfile.java b/vpn/java/android/net/vpn/OpenvpnProfile.java
new file mode 100644
index 0000000..6a106db
--- /dev/null
+++ b/vpn/java/android/net/vpn/OpenvpnProfile.java
@@ -0,0 +1,174 @@
+/*
+ * 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 android.net.vpn;
+
+import android.os.Parcel;
+import android.security.CertTool;
+
+/**
+ * The profile for Openvpn type of VPN.
+ * {@hide}
+ */
+public class OpenvpnProfile extends VpnProfile {
+ private static final long serialVersionUID = 1L;
+ private static final String PROTO_UDP = "udp";
+ private static final String PROTO_TCP = "tcp";
+
+ // Standard Settings
+ private boolean mUserAuth = false;
+ private String mCA;
+ private String mCert;
+ // Advanced Settings
+ private int mPort = 1194;
+ private String mProto = PROTO_UDP;
+ private boolean mUseCompLzo = false;
+ private boolean mSupplyAddr = false;
+ private String mLocalAddr;
+ private String mRemoteAddr;
+
+ @Override
+ public VpnType getType() {
+ return VpnType.OPENVPN;
+ }
+
+ public void setPort(String port) {
+ try {
+ mPort = Integer.parseInt(port);
+ } catch (NumberFormatException e) {
+ // no update
+ }
+ }
+
+ public String getPort() {
+ return Integer.toString(mPort);
+ }
+
+ public String getProto() {
+ return mProto;
+ }
+
+ public CharSequence[] getProtoList() {
+ String[] s = new String[2];
+ s[0] = PROTO_UDP;
+ s[1] = PROTO_TCP;
+ return s;
+ }
+
+ public void setProto(String p) {
+ if (p.contains(PROTO_TCP))
+ mProto = PROTO_TCP;
+ else if(p.contains(PROTO_UDP))
+ mProto = PROTO_UDP;
+ }
+
+
+ public boolean getUserAuth() {
+ return mUserAuth;
+ }
+
+ public void setUserAuth(boolean auth) {
+ mUserAuth = auth;
+ }
+
+ public String getCAFile() {
+ return CertTool.getInstance().getCaCertificate(mCA) ;
+ }
+
+ public String getCAName() {
+ return mCA;
+ }
+
+ public void setCAName(String name) {
+ mCA = name;
+ }
+
+ public String getCertFile() {
+ return CertTool.getInstance().getUserCertificate(mCert);
+ }
+
+ public String getCertName() {
+ return mCert;
+ }
+
+ public void setCertName(String name) {
+ mCert = name;
+ }
+
+ public String getKeyFile() {
+ return CertTool.getInstance().getUserPrivateKey(mCert);
+ }
+
+ public void setUseCompLzo(boolean b) {
+ mUseCompLzo = b;
+ }
+
+ public boolean getUseCompLzo() {
+ return mUseCompLzo;
+ }
+
+ public void setSupplyAddr(boolean b) {
+ mSupplyAddr = b;
+ }
+
+ public boolean getSupplyAddr() {
+ return mSupplyAddr;
+ }
+
+ public void setLocalAddr(String addr) {
+ mLocalAddr = addr;
+ }
+
+ public String getLocalAddr() {
+ return mLocalAddr;
+ }
+
+ public void setRemoteAddr(String addr) {
+ mRemoteAddr = addr;
+ }
+
+ public String getRemoteAddr() {
+ return mRemoteAddr;
+ }
+
+ @Override
+ protected void readFromParcel(Parcel in) {
+ super.readFromParcel(in);
+ mPort = in.readInt();
+ mProto = in.readString();
+ mUserAuth = in.readInt() == 1;
+ mCA = in.readString();
+ mCert = in.readString();
+ mUseCompLzo = in.readInt() == 1;
+ mSupplyAddr = in.readInt() == 1;
+ mLocalAddr = in.readString();
+ mRemoteAddr = in.readString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ super.writeToParcel(parcel, flags);
+ parcel.writeInt(mPort);
+ parcel.writeString(mProto);
+ parcel.writeInt(mUserAuth ? 1 : 0);
+ parcel.writeString(mCA);
+ parcel.writeString(mCert);
+ parcel.writeInt(mUseCompLzo ? 1 : 0);
+ parcel.writeInt(mSupplyAddr ? 1 : 0);
+ parcel.writeString(mLocalAddr);
+ parcel.writeString(mRemoteAddr);
+ }
+}
diff --git a/vpn/java/android/net/vpn/VpnType.java b/vpn/java/android/net/vpn/VpnType.java
index 356f8b1..53ef5fe 100644
--- a/vpn/java/android/net/vpn/VpnType.java
+++ b/vpn/java/android/net/vpn/VpnType.java
@@ -29,6 +29,9 @@ public enum VpnType {
L2tpIpsecPskProfile.class),
L2TP_IPSEC("L2TP/IPSec CRT", R.string.l2tp_ipsec_crt_vpn_description,
L2tpIpsecProfile.class);
+ L2TP_IPSEC("L2TP/IPSec CRT", "Certificate based L2TP/IPSec VPN",
+ L2tpIpsecProfile.class),
+ OPENVPN("OpenVPN", "", OpenvpnProfile.class);
private String mDisplayName;
private int mDescriptionId;