blob: 55ba8e27354d9ba54ec9e51637abdfc3da27ae7c [file] [log] [blame]
Chia-chi Yehff3bdca2011-05-23 17:26:46 -07001/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.connectivity;
18
19import android.app.Notification;
20import android.app.NotificationManager;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070021import android.content.Context;
22import android.content.Intent;
23import android.content.pm.ApplicationInfo;
24import android.content.pm.PackageManager;
25import android.content.res.Resources;
26import android.graphics.Bitmap;
27import android.graphics.Canvas;
28import android.graphics.drawable.Drawable;
29import android.net.INetworkManagementEventObserver;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070030import android.net.LocalSocket;
31import android.net.LocalSocketAddress;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070032import android.os.Binder;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070033import android.os.ParcelFileDescriptor;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070034import android.os.Process;
35import android.os.SystemClock;
36import android.os.SystemProperties;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070037import android.util.Log;
38
39import com.android.internal.R;
Chia-chi Yeh2e467642011-07-04 03:23:12 -070040import com.android.internal.net.LegacyVpnInfo;
Chia-chi Yeh04ba25c2011-06-15 17:07:27 -070041import com.android.internal.net.VpnConfig;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070042import com.android.server.ConnectivityService.VpnCallback;
43
Chia-chi Yeh97a61562011-07-14 15:05:05 -070044import java.io.File;
45import java.io.FileInputStream;
46import java.io.InputStream;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070047import java.io.OutputStream;
48import java.nio.charset.Charsets;
Chia-chi Yeh41d16852011-07-01 02:12:06 -070049import java.util.Arrays;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070050
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070051/**
52 * @hide
53 */
54public class Vpn extends INetworkManagementEventObserver.Stub {
55
56 private final static String TAG = "Vpn";
57 private final static String VPN = android.Manifest.permission.VPN;
58
59 private final Context mContext;
60 private final VpnCallback mCallback;
61
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -070062 private String mPackage = VpnConfig.LEGACY_VPN;
63 private String mInterface;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070064 private LegacyVpnRunner mLegacyVpnRunner;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070065
66 public Vpn(Context context, VpnCallback callback) {
67 mContext = context;
68 mCallback = callback;
69 }
70
71 /**
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070072 * Protect a socket from routing changes by binding it to the given
Chia-chi Yeh3f3337a2011-06-17 16:34:32 -070073 * interface. The socket IS closed by this method.
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070074 *
75 * @param socket The socket to be bound.
76 * @param name The name of the interface.
77 */
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -070078 public void protect(ParcelFileDescriptor socket, String interfaze) {
Chia-chi Yeh3f3337a2011-06-17 16:34:32 -070079 try {
80 mContext.enforceCallingPermission(VPN, "protect");
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -070081 jniProtect(socket.getFd(), interfaze);
Chia-chi Yeh3f3337a2011-06-17 16:34:32 -070082 } finally {
83 try {
84 socket.close();
85 } catch (Exception e) {
86 // ignore
87 }
88 }
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070089 }
90
91 /**
Chia-chi Yeh100155a2011-07-03 16:52:38 -070092 * Prepare for a VPN application. This method is designed to solve
93 * race conditions. It first compares the current prepared package
94 * with {@code oldPackage}. If they are the same, the prepared
95 * package is revoked and replaced with {@code newPackage}. If
96 * {@code oldPackage} is {@code null}, the comparison is omitted.
97 * If {@code newPackage} is the same package or {@code null}, the
98 * revocation is omitted. This method returns {@code true} if the
99 * operation is succeeded.
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700100 *
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700101 * Legacy VPN is handled specially since it is not a real package.
102 * It uses {@link VpnConfig#LEGACY_VPN} as its package name, and
103 * it can be revoked by itself.
104 *
105 * @param oldPackage The package name of the old VPN application.
106 * @param newPackage The package name of the new VPN application.
107 * @return true if the operation is succeeded.
Chia-chi Yehe9107902011-07-02 01:48:50 -0700108 */
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700109 public synchronized boolean prepare(String oldPackage, String newPackage) {
110 // Return false if the package does not match.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700111 if (oldPackage != null && !oldPackage.equals(mPackage)) {
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700112 return false;
Chia-chi Yehe9107902011-07-02 01:48:50 -0700113 }
114
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700115 // Return true if we do not need to revoke.
116 if (newPackage == null ||
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700117 (newPackage.equals(mPackage) && !newPackage.equals(VpnConfig.LEGACY_VPN))) {
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700118 return true;
119 }
120
121 // Only system user can revoke a package.
Chia-chi Yehe9107902011-07-02 01:48:50 -0700122 if (Binder.getCallingUid() != Process.SYSTEM_UID) {
123 throw new SecurityException("Unauthorized Caller");
124 }
125
126 // Check the permission of the given package.
127 PackageManager pm = mContext.getPackageManager();
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700128 if (!newPackage.equals(VpnConfig.LEGACY_VPN) &&
129 pm.checkPermission(VPN, newPackage) != PackageManager.PERMISSION_GRANTED) {
130 throw new SecurityException(newPackage + " does not have " + VPN);
Chia-chi Yehe9107902011-07-02 01:48:50 -0700131 }
132
133 // Reset the interface and hide the notification.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700134 if (mInterface != null) {
135 jniReset(mInterface);
Chia-chi Yehe9107902011-07-02 01:48:50 -0700136 mCallback.restore();
137 hideNotification();
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700138 mInterface = null;
Chia-chi Yehe9107902011-07-02 01:48:50 -0700139 }
140
141 // Send out the broadcast or stop LegacyVpnRunner.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700142 if (!mPackage.equals(VpnConfig.LEGACY_VPN)) {
Chia-chi Yehe9107902011-07-02 01:48:50 -0700143 Intent intent = new Intent(VpnConfig.ACTION_VPN_REVOKED);
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700144 intent.setPackage(mPackage);
Chia-chi Yehe9107902011-07-02 01:48:50 -0700145 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
146 mContext.sendBroadcast(intent);
147 } else if (mLegacyVpnRunner != null) {
148 mLegacyVpnRunner.exit();
149 mLegacyVpnRunner = null;
150 }
151
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700152 Log.i(TAG, "Switched from " + mPackage + " to " + newPackage);
153 mPackage = newPackage;
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700154 return true;
Chia-chi Yehe9107902011-07-02 01:48:50 -0700155 }
156
157 /**
158 * Establish a VPN network and return the file descriptor of the VPN
159 * interface. This methods returns {@code null} if the application is
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700160 * revoked or not prepared.
Chia-chi Yehe9107902011-07-02 01:48:50 -0700161 *
162 * @param config The parameters to configure the network.
163 * @return The file descriptor of the VPN interface.
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700164 */
Chia-chi Yeh04ba25c2011-06-15 17:07:27 -0700165 public synchronized ParcelFileDescriptor establish(VpnConfig config) {
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700166 // Check the permission of the caller.
167 mContext.enforceCallingPermission(VPN, "establish");
168
169 // Check if the caller is already prepared.
170 PackageManager pm = mContext.getPackageManager();
171 ApplicationInfo app = null;
172 try {
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700173 app = pm.getApplicationInfo(mPackage, 0);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700174 } catch (Exception e) {
Chia-chi Yeh7b0b8342011-06-17 14:34:11 -0700175 return null;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700176 }
177 if (Binder.getCallingUid() != app.uid) {
Chia-chi Yeh7b0b8342011-06-17 14:34:11 -0700178 return null;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700179 }
180
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700181 // Load the label.
182 String label = app.loadLabel(pm).toString();
183
184 // Load the icon and convert it into a bitmap.
185 Drawable icon = app.loadIcon(pm);
186 Bitmap bitmap = null;
187 if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) {
188 int width = mContext.getResources().getDimensionPixelSize(
189 android.R.dimen.notification_large_icon_width);
190 int height = mContext.getResources().getDimensionPixelSize(
191 android.R.dimen.notification_large_icon_height);
192 icon.setBounds(0, 0, width, height);
193 bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
194 icon.draw(new Canvas(bitmap));
195 }
196
Chia-chi Yehe9107902011-07-02 01:48:50 -0700197 // Configure the interface. Abort if any of these steps fails.
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700198 ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu));
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700199 try {
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700200 String interfaze = jniGetName(tun.getFd());
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700201 if (jniSetAddresses(interfaze, config.addresses) < 1) {
202 throw new IllegalArgumentException("At least one address must be specified");
203 }
204 if (config.routes != null) {
205 jniSetRoutes(interfaze, config.routes);
206 }
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700207 if (mInterface != null && !mInterface.equals(interfaze)) {
208 jniReset(mInterface);
Chia-chi Yehf4e3bf82011-06-30 12:33:17 -0700209 }
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700210 mInterface = interfaze;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700211 } catch (RuntimeException e) {
212 try {
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700213 tun.close();
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700214 } catch (Exception ex) {
215 // ignore
216 }
217 throw e;
218 }
219
Chia-chi Yeh8909b102011-07-01 01:09:42 -0700220 // Override DNS servers and search domains.
221 mCallback.override(config.dnsServers, config.searchDomains);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700222
Chia-chi Yeh8909b102011-07-01 01:09:42 -0700223 // Fill more values.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700224 config.packagz = mPackage;
225 config.interfaze = mInterface;
Chia-chi Yeh8909b102011-07-01 01:09:42 -0700226
227 // Show the notification!
Chia-chi Yeh383e0522011-07-01 00:13:25 -0700228 showNotification(config, label, bitmap);
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700229 return tun;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700230 }
231
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700232 // INetworkManagementEventObserver.Stub
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700233 public void interfaceStatusChanged(String interfaze, boolean up) {
Mike J. Chenf59c7d02011-06-23 15:33:15 -0700234 }
235
236 // INetworkManagementEventObserver.Stub
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700237 public void interfaceLinkStateChanged(String interfaze, boolean up) {
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700238 }
239
240 // INetworkManagementEventObserver.Stub
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700241 public void interfaceAdded(String interfaze) {
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700242 }
243
244 // INetworkManagementEventObserver.Stub
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700245 public synchronized void interfaceRemoved(String interfaze) {
246 if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) {
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700247 mCallback.restore();
Chia-chi Yehe9107902011-07-02 01:48:50 -0700248 hideNotification();
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700249 mInterface = null;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700250 }
251 }
252
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700253 private void showNotification(VpnConfig config, String label, Bitmap icon) {
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700254 NotificationManager nm = (NotificationManager)
255 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
256
257 if (nm != null) {
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700258 String title = (label == null) ? mContext.getString(R.string.vpn_title) :
259 mContext.getString(R.string.vpn_title_long, label);
Chia-chi Yeh34e78132011-07-03 03:07:07 -0700260 String text = (config.session == null) ? mContext.getString(R.string.vpn_text) :
261 mContext.getString(R.string.vpn_text_long, config.session);
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700262 config.startTime = SystemClock.elapsedRealtime();
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700263
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700264 long identity = Binder.clearCallingIdentity();
265 Notification notification = new Notification.Builder(mContext)
266 .setSmallIcon(R.drawable.vpn_connected)
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700267 .setLargeIcon(icon)
268 .setContentTitle(title)
Chia-chi Yehf8905fd2011-06-14 16:35:02 -0700269 .setContentText(text)
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700270 .setContentIntent(VpnConfig.getIntentForStatusPanel(mContext, config))
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700271 .setDefaults(Notification.DEFAULT_ALL)
272 .setOngoing(true)
273 .getNotification();
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700274 nm.notify(R.drawable.vpn_connected, notification);
275 Binder.restoreCallingIdentity(identity);
276 }
277 }
278
279 private void hideNotification() {
280 NotificationManager nm = (NotificationManager)
281 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
282
283 if (nm != null) {
284 long identity = Binder.clearCallingIdentity();
285 nm.cancel(R.drawable.vpn_connected);
286 Binder.restoreCallingIdentity(identity);
287 }
288 }
289
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700290 private native int jniCreate(int mtu);
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700291 private native String jniGetName(int tun);
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700292 private native int jniSetAddresses(String interfaze, String addresses);
293 private native int jniSetRoutes(String interfaze, String routes);
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700294 private native void jniReset(String interfaze);
295 private native int jniCheck(String interfaze);
296 private native void jniProtect(int socket, String interfaze);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700297
298 /**
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700299 * Start legacy VPN. This method stops the daemons and restart them
300 * if arguments are not null. Heavy things are offloaded to another
Chia-chi Yehe9107902011-07-02 01:48:50 -0700301 * thread, so callers will not be blocked for a long time.
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700302 *
Chia-chi Yehe9107902011-07-02 01:48:50 -0700303 * @param config The parameters to configure the network.
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700304 * @param raoocn The arguments to be passed to racoon.
305 * @param mtpd The arguments to be passed to mtpd.
306 */
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700307 public synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700308 // Prepare for the new request. This also checks the caller.
309 prepare(null, VpnConfig.LEGACY_VPN);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700310
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700311 // Start a new LegacyVpnRunner and we are done!
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700312 mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd);
313 mLegacyVpnRunner.start();
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700314 }
315
316 /**
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700317 * Return the information of the current ongoing legacy VPN.
318 */
319 public synchronized LegacyVpnInfo getLegacyVpnInfo() {
320 // Only system user can call this method.
321 if (Binder.getCallingUid() != Process.SYSTEM_UID) {
322 throw new SecurityException("Unauthorized Caller");
323 }
324 return (mLegacyVpnRunner == null) ? null : mLegacyVpnRunner.getInfo();
325 }
326
327 /**
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700328 * Bringing up a VPN connection takes time, and that is all this thread
329 * does. Here we have plenty of time. The only thing we need to take
330 * care of is responding to interruptions as soon as possible. Otherwise
331 * requests will be piled up. This can be done in a Handler as a state
332 * machine, but it is much easier to read in the current form.
333 */
334 private class LegacyVpnRunner extends Thread {
335 private static final String TAG = "LegacyVpnRunner";
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700336
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700337 private final VpnConfig mConfig;
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700338 private final String[] mDaemons;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700339 private final String[][] mArguments;
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700340 private final LegacyVpnInfo mInfo;
341
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700342 private long mTimer = -1;
343
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700344 public LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700345 super(TAG);
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700346 mConfig = config;
347 mDaemons = new String[] {"racoon", "mtpd"};
348 mArguments = new String[][] {racoon, mtpd};
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700349 mInfo = new LegacyVpnInfo();
Chia-chi Yehe9107902011-07-02 01:48:50 -0700350
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700351 // Legacy VPN is not a real package, so we use it to carry the key.
352 mInfo.key = mConfig.packagz;
Chia-chi Yeh34e78132011-07-03 03:07:07 -0700353 mConfig.packagz = VpnConfig.LEGACY_VPN;
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700354 }
355
356 public void exit() {
Chia-chi Yehe9107902011-07-02 01:48:50 -0700357 // We assume that everything is reset after the daemons die.
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700358 interrupt();
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700359 for (String daemon : mDaemons) {
360 SystemProperties.set("ctl.stop", daemon);
361 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700362 }
363
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700364 public LegacyVpnInfo getInfo() {
365 // Update the info when VPN is disconnected.
366 if (mInfo.state == LegacyVpnInfo.STATE_CONNECTED && mInterface == null) {
367 mInfo.state = LegacyVpnInfo.STATE_DISCONNECTED;
368 mInfo.intent = null;
369 }
370 return mInfo;
371 }
372
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700373 @Override
374 public void run() {
375 // Wait for the previous thread since it has been interrupted.
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700376 Log.v(TAG, "Waiting");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700377 synchronized (TAG) {
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700378 Log.v(TAG, "Executing");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700379 execute();
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700380 }
381 }
382
383 private void checkpoint(boolean yield) throws InterruptedException {
384 long now = SystemClock.elapsedRealtime();
385 if (mTimer == -1) {
386 mTimer = now;
387 Thread.sleep(1);
388 } else if (now - mTimer <= 30000) {
389 Thread.sleep(yield ? 200 : 1);
390 } else {
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700391 mInfo.state = LegacyVpnInfo.STATE_TIMEOUT;
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700392 throw new IllegalStateException("Time is up");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700393 }
394 }
395
396 private void execute() {
397 // Catch all exceptions so we can clean up few things.
398 try {
399 // Initialize the timer.
400 checkpoint(false);
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700401 mInfo.state = LegacyVpnInfo.STATE_INITIALIZING;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700402
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700403 // First stop the daemons.
404 for (String daemon : mDaemons) {
405 SystemProperties.set("ctl.stop", daemon);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700406 }
407
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700408 // Wait for the daemons to stop.
409 for (String daemon : mDaemons) {
410 String key = "init.svc." + daemon;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700411 while (!"stopped".equals(SystemProperties.get(key))) {
412 checkpoint(true);
413 }
414 }
415
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700416 // Clear the previous state.
417 File state = new File("/data/misc/vpn/state");
418 state.delete();
419 if (state.exists()) {
420 throw new IllegalStateException("Cannot delete the state");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700421 }
422
Chia-chi Yehe9107902011-07-02 01:48:50 -0700423 // Check if we need to restart any of the daemons.
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700424 boolean restart = false;
425 for (String[] arguments : mArguments) {
426 restart = restart || (arguments != null);
427 }
428 if (!restart) {
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700429 mInfo.state = LegacyVpnInfo.STATE_DISCONNECTED;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700430 return;
431 }
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700432 mInfo.state = LegacyVpnInfo.STATE_CONNECTING;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700433
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700434 // Start the daemon with arguments.
435 for (int i = 0; i < mDaemons.length; ++i) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700436 String[] arguments = mArguments[i];
437 if (arguments == null) {
438 continue;
439 }
440
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700441 // Start the daemon.
442 String daemon = mDaemons[i];
443 SystemProperties.set("ctl.start", daemon);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700444
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700445 // Wait for the daemon to start.
446 String key = "init.svc." + daemon;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700447 while (!"running".equals(SystemProperties.get(key))) {
448 checkpoint(true);
449 }
450
451 // Create the control socket.
452 LocalSocket socket = new LocalSocket();
453 LocalSocketAddress address = new LocalSocketAddress(
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700454 daemon, LocalSocketAddress.Namespace.RESERVED);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700455
456 // Wait for the socket to connect.
457 while (true) {
458 try {
459 socket.connect(address);
460 break;
461 } catch (Exception e) {
462 // ignore
463 }
464 checkpoint(true);
465 }
466 socket.setSoTimeout(500);
467
468 // Send over the arguments.
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700469 OutputStream out = socket.getOutputStream();
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700470 for (String argument : arguments) {
471 byte[] bytes = argument.getBytes(Charsets.UTF_8);
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700472 if (bytes.length > 0xFFFF) {
473 throw new IllegalArgumentException("Argument is too large");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700474 }
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700475 out.write(bytes.length >> 8);
476 out.write(bytes.length);
477 out.write(bytes);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700478 checkpoint(false);
479 }
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700480 out.flush();
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700481 socket.shutdownOutput();
482
483 // Wait for End-of-File.
484 InputStream in = socket.getInputStream();
485 while (true) {
486 try {
487 if (in.read() == -1) {
488 break;
489 }
490 } catch (Exception e) {
491 // ignore
492 }
493 checkpoint(true);
494 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700495 socket.close();
496 }
497
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700498 // Wait for the daemons to create the new state.
499 while (!state.exists()) {
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700500 // Check if a running daemon is dead.
501 for (int i = 0; i < mDaemons.length; ++i) {
502 String daemon = mDaemons[i];
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700503 if (mArguments[i] != null && !"running".equals(
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700504 SystemProperties.get("init.svc." + daemon))) {
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700505 throw new IllegalStateException(daemon + " is dead");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700506 }
507 }
508 checkpoint(true);
509 }
510
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700511 // Now we are connected. Read and parse the new state.
512 byte[] buffer = new byte[(int) state.length()];
513 if (new FileInputStream(state).read(buffer) != buffer.length) {
514 throw new IllegalStateException("Cannot read the state");
515 }
516 String[] parameters = new String(buffer, Charsets.UTF_8).split("\n", -1);
517 if (parameters.length != 6) {
518 throw new IllegalStateException("Cannot parse the state");
519 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700520
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700521 // Set the interface and the addresses in the config.
522 mConfig.interfaze = parameters[0].trim();
523 mConfig.addresses = parameters[1].trim();
524
525 // Set the routes if they are not set in the config.
526 if (mConfig.routes == null || mConfig.routes.isEmpty()) {
527 mConfig.routes = parameters[2].trim();
528 }
529
530 // Set the DNS servers if they are not set in the config.
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700531 if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700532 String dnsServers = parameters[3].trim();
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700533 if (!dnsServers.isEmpty()) {
534 mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
535 }
536 }
537
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700538 // Set the search domains if they are not set in the config.
539 if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) {
540 String searchDomains = parameters[4].trim();
541 if (!searchDomains.isEmpty()) {
542 mConfig.searchDomains = Arrays.asList(searchDomains.split(" "));
543 }
544 }
Chia-chi Yehe9107902011-07-02 01:48:50 -0700545
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700546 // Set the routes.
547 jniSetRoutes(mConfig.interfaze, mConfig.routes);
548
549 // Here is the last step and it must be done synchronously.
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700550 synchronized (Vpn.this) {
551 // Check if the thread is interrupted while we are waiting.
552 checkpoint(false);
553
Chia-chi Yehe9107902011-07-02 01:48:50 -0700554 // Check if the interface is gone while we are waiting.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700555 if (jniCheck(mConfig.interfaze) == 0) {
Chia-chi Yeh34e78132011-07-03 03:07:07 -0700556 throw new IllegalStateException(mConfig.interfaze + " is gone");
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700557 }
Chia-chi Yehe9107902011-07-02 01:48:50 -0700558
559 // Now INetworkManagementEventObserver is watching our back.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700560 mInterface = mConfig.interfaze;
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700561 mCallback.override(mConfig.dnsServers, mConfig.searchDomains);
562 showNotification(mConfig, null, null);
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700563
564 Log.i(TAG, "Connected!");
565 mInfo.state = LegacyVpnInfo.STATE_CONNECTED;
566 mInfo.intent = VpnConfig.getIntentForStatusPanel(mContext, null);
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700567 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700568 } catch (Exception e) {
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700569 Log.i(TAG, "Aborting", e);
Chia-chi Yehe9107902011-07-02 01:48:50 -0700570 exit();
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700571 } finally {
572 // Do not leave an unstable state.
573 if (mInfo.state == LegacyVpnInfo.STATE_INITIALIZING ||
574 mInfo.state == LegacyVpnInfo.STATE_CONNECTING) {
575 mInfo.state = LegacyVpnInfo.STATE_FAILED;
576 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700577 }
578 }
579 }
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700580}