blob: b3cbb844bd1c205fe33cd8eb4c1366e05d19b24a [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
Jeff Sharkey899223b2012-08-04 15:24:58 -070019import static android.Manifest.permission.BIND_VPN_SERVICE;
20
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070021import android.app.Notification;
22import android.app.NotificationManager;
Jeff Sharkey899223b2012-08-04 15:24:58 -070023import android.app.PendingIntent;
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -070024import android.content.ComponentName;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070025import android.content.Context;
26import android.content.Intent;
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -070027import android.content.ServiceConnection;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070028import android.content.pm.ApplicationInfo;
29import android.content.pm.PackageManager;
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -070030import android.content.pm.ResolveInfo;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070031import android.graphics.Bitmap;
32import android.graphics.Canvas;
33import android.graphics.drawable.Drawable;
Jeff Sharkey899223b2012-08-04 15:24:58 -070034import android.net.BaseNetworkStateTracker;
35import android.net.ConnectivityManager;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070036import android.net.INetworkManagementEventObserver;
Jeff Sharkey82f85212012-08-24 11:17:25 -070037import android.net.LinkProperties;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070038import android.net.LocalSocket;
39import android.net.LocalSocketAddress;
Jeff Sharkey899223b2012-08-04 15:24:58 -070040import android.net.NetworkInfo;
Jeff Sharkey82f85212012-08-24 11:17:25 -070041import android.net.RouteInfo;
Jeff Sharkey899223b2012-08-04 15:24:58 -070042import android.net.NetworkInfo.DetailedState;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070043import android.os.Binder;
Chia-chi Yehc1bac3a2011-12-16 15:00:31 -080044import android.os.FileUtils;
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -070045import android.os.IBinder;
Jeff Sharkey899223b2012-08-04 15:24:58 -070046import android.os.INetworkManagementService;
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -070047import android.os.Parcel;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070048import android.os.ParcelFileDescriptor;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070049import android.os.Process;
Jeff Sharkey899223b2012-08-04 15:24:58 -070050import android.os.RemoteException;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070051import android.os.SystemClock;
Jeff Sharkey088f29f2012-08-05 14:55:04 -070052import android.os.SystemService;
Jeff Sharkey82f85212012-08-24 11:17:25 -070053import android.security.Credentials;
54import android.security.KeyStore;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070055import android.util.Log;
Jeff Sharkey82f85212012-08-24 11:17:25 -070056import android.widget.Toast;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070057
58import com.android.internal.R;
Chia-chi Yeh2e467642011-07-04 03:23:12 -070059import com.android.internal.net.LegacyVpnInfo;
Chia-chi Yeh04ba25c2011-06-15 17:07:27 -070060import com.android.internal.net.VpnConfig;
Jeff Sharkey82f85212012-08-24 11:17:25 -070061import com.android.internal.net.VpnProfile;
Jeff Sharkey899223b2012-08-04 15:24:58 -070062import com.android.internal.util.Preconditions;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070063import com.android.server.ConnectivityService.VpnCallback;
Jeff Sharkey899223b2012-08-04 15:24:58 -070064import com.android.server.net.BaseNetworkObserver;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070065
Chia-chi Yeh97a61562011-07-14 15:05:05 -070066import java.io.File;
Chia-chi Yeh97a61562011-07-14 15:05:05 -070067import java.io.InputStream;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070068import java.io.OutputStream;
Jeff Sharkey82f85212012-08-24 11:17:25 -070069import java.net.Inet4Address;
70import java.net.InetAddress;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070071import java.nio.charset.Charsets;
Chia-chi Yeh41d16852011-07-01 02:12:06 -070072import java.util.Arrays;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070073
Jeff Sharkey065b2992012-08-05 14:16:48 -070074import libcore.io.IoUtils;
75
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070076/**
77 * @hide
78 */
Jeff Sharkey899223b2012-08-04 15:24:58 -070079public class Vpn extends BaseNetworkStateTracker {
80 private static final String TAG = "Vpn";
81 private static final boolean LOGD = true;
82
83 // TODO: create separate trackers for each unique VPN to support
84 // automated reconnection
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070085
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070086 private final VpnCallback mCallback;
87
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -070088 private String mPackage = VpnConfig.LEGACY_VPN;
89 private String mInterface;
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -070090 private Connection mConnection;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070091 private LegacyVpnRunner mLegacyVpnRunner;
Jeff Sharkey899223b2012-08-04 15:24:58 -070092 private PendingIntent mStatusIntent;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070093 private boolean mEnableNotif = true;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070094
Jeff Sharkey899223b2012-08-04 15:24:58 -070095 public Vpn(Context context, VpnCallback callback, INetworkManagementService netService) {
96 // TODO: create dedicated TYPE_VPN network type
97 super(ConnectivityManager.TYPE_DUMMY);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070098 mContext = context;
99 mCallback = callback;
Jeff Sharkey899223b2012-08-04 15:24:58 -0700100
101 try {
102 netService.registerObserver(mObserver);
103 } catch (RemoteException e) {
104 Log.wtf(TAG, "Problem registering observer", e);
105 }
106 }
107
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700108 public void setEnableNotifications(boolean enableNotif) {
109 mEnableNotif = enableNotif;
110 }
111
Jeff Sharkey899223b2012-08-04 15:24:58 -0700112 @Override
113 protected void startMonitoringInternal() {
114 // Ignored; events are sent through callbacks for now
115 }
116
117 @Override
118 public boolean teardown() {
119 // TODO: finish migration to unique tracker for each VPN
120 throw new UnsupportedOperationException();
121 }
122
123 @Override
124 public boolean reconnect() {
125 // TODO: finish migration to unique tracker for each VPN
126 throw new UnsupportedOperationException();
127 }
128
129 @Override
130 public String getTcpBufferSizesPropName() {
131 return PROP_TCP_BUFFER_UNKNOWN;
132 }
133
134 /**
135 * Update current state, dispaching event to listeners.
136 */
137 private void updateState(DetailedState detailedState, String reason) {
138 if (LOGD) Log.d(TAG, "setting state=" + detailedState + ", reason=" + reason);
139 mNetworkInfo.setDetailedState(detailedState, reason, null);
140 mCallback.onStateChanged(new NetworkInfo(mNetworkInfo));
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700141 }
142
143 /**
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700144 * Prepare for a VPN application. This method is designed to solve
145 * race conditions. It first compares the current prepared package
146 * with {@code oldPackage}. If they are the same, the prepared
147 * package is revoked and replaced with {@code newPackage}. If
148 * {@code oldPackage} is {@code null}, the comparison is omitted.
149 * If {@code newPackage} is the same package or {@code null}, the
150 * revocation is omitted. This method returns {@code true} if the
151 * operation is succeeded.
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700152 *
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700153 * Legacy VPN is handled specially since it is not a real package.
154 * It uses {@link VpnConfig#LEGACY_VPN} as its package name, and
155 * it can be revoked by itself.
156 *
157 * @param oldPackage The package name of the old VPN application.
158 * @param newPackage The package name of the new VPN application.
159 * @return true if the operation is succeeded.
Chia-chi Yehe9107902011-07-02 01:48:50 -0700160 */
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700161 public synchronized boolean prepare(String oldPackage, String newPackage) {
162 // Return false if the package does not match.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700163 if (oldPackage != null && !oldPackage.equals(mPackage)) {
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700164 return false;
Chia-chi Yehe9107902011-07-02 01:48:50 -0700165 }
166
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700167 // Return true if we do not need to revoke.
168 if (newPackage == null ||
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700169 (newPackage.equals(mPackage) && !newPackage.equals(VpnConfig.LEGACY_VPN))) {
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700170 return true;
171 }
172
Chia-chi Yehdadc8572012-06-08 13:05:58 -0700173 // Check if the caller is authorized.
174 enforceControlPermission();
Chia-chi Yehe9107902011-07-02 01:48:50 -0700175
Chia-chi Yehe9107902011-07-02 01:48:50 -0700176 // Reset the interface and hide the notification.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700177 if (mInterface != null) {
178 jniReset(mInterface);
Jeff Sharkey899223b2012-08-04 15:24:58 -0700179 final long token = Binder.clearCallingIdentity();
180 try {
181 mCallback.restore();
182 hideNotification();
183 } finally {
184 Binder.restoreCallingIdentity(token);
185 }
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700186 mInterface = null;
Chia-chi Yehe9107902011-07-02 01:48:50 -0700187 }
188
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700189 // Revoke the connection or stop LegacyVpnRunner.
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700190 if (mConnection != null) {
191 try {
192 mConnection.mService.transact(IBinder.LAST_CALL_TRANSACTION,
193 Parcel.obtain(), null, IBinder.FLAG_ONEWAY);
194 } catch (Exception e) {
195 // ignore
196 }
197 mContext.unbindService(mConnection);
198 mConnection = null;
Chia-chi Yehe9107902011-07-02 01:48:50 -0700199 } else if (mLegacyVpnRunner != null) {
200 mLegacyVpnRunner.exit();
201 mLegacyVpnRunner = null;
202 }
203
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700204 Log.i(TAG, "Switched from " + mPackage + " to " + newPackage);
205 mPackage = newPackage;
Jeff Sharkey899223b2012-08-04 15:24:58 -0700206 updateState(DetailedState.IDLE, "prepare");
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700207 return true;
Chia-chi Yehe9107902011-07-02 01:48:50 -0700208 }
209
210 /**
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700211 * Protect a socket from routing changes by binding it to the given
212 * interface. The socket is NOT closed by this method.
213 *
214 * @param socket The socket to be bound.
Jeff Sharkey899223b2012-08-04 15:24:58 -0700215 * @param interfaze The name of the interface.
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700216 */
217 public void protect(ParcelFileDescriptor socket, String interfaze) throws Exception {
218 PackageManager pm = mContext.getPackageManager();
219 ApplicationInfo app = pm.getApplicationInfo(mPackage, 0);
220 if (Binder.getCallingUid() != app.uid) {
221 throw new SecurityException("Unauthorized Caller");
222 }
223 jniProtect(socket.getFd(), interfaze);
224 }
225
226 /**
Chia-chi Yehe9107902011-07-02 01:48:50 -0700227 * Establish a VPN network and return the file descriptor of the VPN
228 * interface. This methods returns {@code null} if the application is
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700229 * revoked or not prepared.
Chia-chi Yehe9107902011-07-02 01:48:50 -0700230 *
231 * @param config The parameters to configure the network.
232 * @return The file descriptor of the VPN interface.
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700233 */
Chia-chi Yeh04ba25c2011-06-15 17:07:27 -0700234 public synchronized ParcelFileDescriptor establish(VpnConfig config) {
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700235 // Check if the caller is already prepared.
236 PackageManager pm = mContext.getPackageManager();
237 ApplicationInfo app = null;
238 try {
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700239 app = pm.getApplicationInfo(mPackage, 0);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700240 } catch (Exception e) {
Chia-chi Yeh7b0b8342011-06-17 14:34:11 -0700241 return null;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700242 }
243 if (Binder.getCallingUid() != app.uid) {
Chia-chi Yeh7b0b8342011-06-17 14:34:11 -0700244 return null;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700245 }
246
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700247 // Check if the service is properly declared.
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700248 Intent intent = new Intent(VpnConfig.SERVICE_INTERFACE);
249 intent.setClassName(mPackage, config.user);
250 ResolveInfo info = pm.resolveService(intent, 0);
251 if (info == null) {
252 throw new SecurityException("Cannot find " + config.user);
253 }
254 if (!BIND_VPN_SERVICE.equals(info.serviceInfo.permission)) {
255 throw new SecurityException(config.user + " does not require " + BIND_VPN_SERVICE);
256 }
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700257
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700258 // Load the label.
259 String label = app.loadLabel(pm).toString();
260
261 // Load the icon and convert it into a bitmap.
262 Drawable icon = app.loadIcon(pm);
263 Bitmap bitmap = null;
264 if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) {
265 int width = mContext.getResources().getDimensionPixelSize(
266 android.R.dimen.notification_large_icon_width);
267 int height = mContext.getResources().getDimensionPixelSize(
268 android.R.dimen.notification_large_icon_height);
269 icon.setBounds(0, 0, width, height);
270 bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Dianne Hackborn6311d0a2011-08-02 16:37:58 -0700271 Canvas c = new Canvas(bitmap);
272 icon.draw(c);
273 c.setBitmap(null);
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700274 }
275
Chia-chi Yehe9107902011-07-02 01:48:50 -0700276 // Configure the interface. Abort if any of these steps fails.
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700277 ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu));
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700278 try {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700279 updateState(DetailedState.CONNECTING, "establish");
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700280 String interfaze = jniGetName(tun.getFd());
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700281 if (jniSetAddresses(interfaze, config.addresses) < 1) {
282 throw new IllegalArgumentException("At least one address must be specified");
283 }
284 if (config.routes != null) {
285 jniSetRoutes(interfaze, config.routes);
286 }
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700287 Connection connection = new Connection();
288 if (!mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
289 throw new IllegalStateException("Cannot bind " + config.user);
290 }
291 if (mConnection != null) {
292 mContext.unbindService(mConnection);
293 }
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700294 if (mInterface != null && !mInterface.equals(interfaze)) {
295 jniReset(mInterface);
Chia-chi Yehf4e3bf82011-06-30 12:33:17 -0700296 }
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700297 mConnection = connection;
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700298 mInterface = interfaze;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700299 } catch (RuntimeException e) {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700300 updateState(DetailedState.FAILED, "establish");
Jeff Sharkey065b2992012-08-05 14:16:48 -0700301 IoUtils.closeQuietly(tun);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700302 throw e;
303 }
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700304 Log.i(TAG, "Established by " + config.user + " on " + mInterface);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700305
Chia-chi Yeh8909b102011-07-01 01:09:42 -0700306 // Fill more values.
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700307 config.user = mPackage;
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700308 config.interfaze = mInterface;
Chia-chi Yeh8909b102011-07-01 01:09:42 -0700309
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700310 // Override DNS servers and show the notification.
Jeff Sharkey899223b2012-08-04 15:24:58 -0700311 final long token = Binder.clearCallingIdentity();
312 try {
313 mCallback.override(config.dnsServers, config.searchDomains);
314 showNotification(config, label, bitmap);
315 } finally {
316 Binder.restoreCallingIdentity(token);
317 }
318 // TODO: ensure that contract class eventually marks as connected
319 updateState(DetailedState.AUTHENTICATING, "establish");
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700320 return tun;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700321 }
322
Jeff Sharkey899223b2012-08-04 15:24:58 -0700323 @Deprecated
324 public synchronized void interfaceStatusChanged(String iface, boolean up) {
325 try {
326 mObserver.interfaceStatusChanged(iface, up);
327 } catch (RemoteException e) {
328 // ignored; target is local
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700329 }
330 }
331
Jeff Sharkey899223b2012-08-04 15:24:58 -0700332 private INetworkManagementEventObserver mObserver = new BaseNetworkObserver() {
333 @Override
334 public void interfaceStatusChanged(String interfaze, boolean up) {
335 synchronized (Vpn.this) {
336 if (!up && mLegacyVpnRunner != null) {
337 mLegacyVpnRunner.check(interfaze);
338 }
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700339 }
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700340 }
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700341
Jeff Sharkey899223b2012-08-04 15:24:58 -0700342 @Override
343 public void interfaceRemoved(String interfaze) {
344 synchronized (Vpn.this) {
345 if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) {
346 final long token = Binder.clearCallingIdentity();
347 try {
348 mCallback.restore();
349 hideNotification();
350 } finally {
351 Binder.restoreCallingIdentity(token);
352 }
353 mInterface = null;
354 if (mConnection != null) {
355 mContext.unbindService(mConnection);
356 mConnection = null;
357 updateState(DetailedState.DISCONNECTED, "interfaceRemoved");
358 } else if (mLegacyVpnRunner != null) {
359 mLegacyVpnRunner.exit();
360 mLegacyVpnRunner = null;
361 }
362 }
363 }
364 }
365 };
Haoyu Baidb3c8672012-06-20 14:29:57 -0700366
Chia-chi Yehdadc8572012-06-08 13:05:58 -0700367 private void enforceControlPermission() {
368 // System user is allowed to control VPN.
369 if (Binder.getCallingUid() == Process.SYSTEM_UID) {
370 return;
371 }
372
373 try {
374 // System dialogs are also allowed to control VPN.
375 PackageManager pm = mContext.getPackageManager();
376 ApplicationInfo app = pm.getApplicationInfo(VpnConfig.DIALOGS_PACKAGE, 0);
377 if (Binder.getCallingUid() == app.uid) {
378 return;
379 }
380 } catch (Exception e) {
381 // ignore
382 }
383
384 throw new SecurityException("Unauthorized Caller");
385 }
386
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700387 private class Connection implements ServiceConnection {
388 private IBinder mService;
389
390 @Override
391 public void onServiceConnected(ComponentName name, IBinder service) {
392 mService = service;
393 }
394
395 @Override
396 public void onServiceDisconnected(ComponentName name) {
397 mService = null;
398 }
399 }
400
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700401 private void showNotification(VpnConfig config, String label, Bitmap icon) {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700402 if (!mEnableNotif) return;
Jeff Sharkey899223b2012-08-04 15:24:58 -0700403 mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext, config);
404
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700405 NotificationManager nm = (NotificationManager)
406 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
407
408 if (nm != null) {
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700409 String title = (label == null) ? mContext.getString(R.string.vpn_title) :
410 mContext.getString(R.string.vpn_title_long, label);
Chia-chi Yeh34e78132011-07-03 03:07:07 -0700411 String text = (config.session == null) ? mContext.getString(R.string.vpn_text) :
412 mContext.getString(R.string.vpn_text_long, config.session);
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700413 config.startTime = SystemClock.elapsedRealtime();
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700414
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700415 Notification notification = new Notification.Builder(mContext)
416 .setSmallIcon(R.drawable.vpn_connected)
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700417 .setLargeIcon(icon)
418 .setContentTitle(title)
Chia-chi Yehf8905fd2011-06-14 16:35:02 -0700419 .setContentText(text)
Jeff Sharkey899223b2012-08-04 15:24:58 -0700420 .setContentIntent(mStatusIntent)
Chia-chi Yeh50fe7092012-01-11 14:26:24 -0800421 .setDefaults(0)
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700422 .setOngoing(true)
Jeff Sharkey899223b2012-08-04 15:24:58 -0700423 .build();
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700424 nm.notify(R.drawable.vpn_connected, notification);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700425 }
426 }
427
428 private void hideNotification() {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700429 if (!mEnableNotif) return;
Jeff Sharkey899223b2012-08-04 15:24:58 -0700430 mStatusIntent = null;
431
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700432 NotificationManager nm = (NotificationManager)
433 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
434
435 if (nm != null) {
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700436 nm.cancel(R.drawable.vpn_connected);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700437 }
438 }
439
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700440 private native int jniCreate(int mtu);
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700441 private native String jniGetName(int tun);
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700442 private native int jniSetAddresses(String interfaze, String addresses);
443 private native int jniSetRoutes(String interfaze, String routes);
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700444 private native void jniReset(String interfaze);
445 private native int jniCheck(String interfaze);
446 private native void jniProtect(int socket, String interfaze);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700447
Jeff Sharkey82f85212012-08-24 11:17:25 -0700448 private static String findLegacyVpnGateway(LinkProperties prop) {
449 for (RouteInfo route : prop.getRoutes()) {
450 // Currently legacy VPN only works on IPv4.
451 if (route.isDefaultRoute() && route.getGateway() instanceof Inet4Address) {
452 return route.getGateway().getHostAddress();
453 }
454 }
Jeff Sharkey899223b2012-08-04 15:24:58 -0700455
Jeff Sharkey82f85212012-08-24 11:17:25 -0700456 throw new IllegalStateException("Unable to find suitable gateway");
457 }
458
459 /**
460 * Start legacy VPN, controlling native daemons as needed. Creates a
461 * secondary thread to perform connection work, returning quickly.
462 */
463 public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, LinkProperties egress) {
464 if (keyStore.state() != KeyStore.State.UNLOCKED) {
465 throw new IllegalStateException("KeyStore isn't unlocked");
466 }
467
468 final String iface = egress.getInterfaceName();
469 final String gateway = findLegacyVpnGateway(egress);
470
471 // Load certificates.
472 String privateKey = "";
473 String userCert = "";
474 String caCert = "";
475 String serverCert = "";
476 if (!profile.ipsecUserCert.isEmpty()) {
477 privateKey = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert;
478 byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert);
479 userCert = (value == null) ? null : new String(value, Charsets.UTF_8);
480 }
481 if (!profile.ipsecCaCert.isEmpty()) {
482 byte[] value = keyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert);
483 caCert = (value == null) ? null : new String(value, Charsets.UTF_8);
484 }
485 if (!profile.ipsecServerCert.isEmpty()) {
486 byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecServerCert);
487 serverCert = (value == null) ? null : new String(value, Charsets.UTF_8);
488 }
489 if (privateKey == null || userCert == null || caCert == null || serverCert == null) {
490 throw new IllegalStateException("Cannot load credentials");
491 }
492
493 // Prepare arguments for racoon.
494 String[] racoon = null;
495 switch (profile.type) {
496 case VpnProfile.TYPE_L2TP_IPSEC_PSK:
497 racoon = new String[] {
498 iface, profile.server, "udppsk", profile.ipsecIdentifier,
499 profile.ipsecSecret, "1701",
500 };
501 break;
502 case VpnProfile.TYPE_L2TP_IPSEC_RSA:
503 racoon = new String[] {
504 iface, profile.server, "udprsa", privateKey, userCert,
505 caCert, serverCert, "1701",
506 };
507 break;
508 case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
509 racoon = new String[] {
510 iface, profile.server, "xauthpsk", profile.ipsecIdentifier,
511 profile.ipsecSecret, profile.username, profile.password, "", gateway,
512 };
513 break;
514 case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
515 racoon = new String[] {
516 iface, profile.server, "xauthrsa", privateKey, userCert,
517 caCert, serverCert, profile.username, profile.password, "", gateway,
518 };
519 break;
520 case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
521 racoon = new String[] {
522 iface, profile.server, "hybridrsa",
523 caCert, serverCert, profile.username, profile.password, "", gateway,
524 };
525 break;
526 }
527
528 // Prepare arguments for mtpd.
529 String[] mtpd = null;
530 switch (profile.type) {
531 case VpnProfile.TYPE_PPTP:
532 mtpd = new String[] {
533 iface, "pptp", profile.server, "1723",
534 "name", profile.username, "password", profile.password,
535 "linkname", "vpn", "refuse-eap", "nodefaultroute",
536 "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
537 (profile.mppe ? "+mppe" : "nomppe"),
538 };
539 break;
540 case VpnProfile.TYPE_L2TP_IPSEC_PSK:
541 case VpnProfile.TYPE_L2TP_IPSEC_RSA:
542 mtpd = new String[] {
543 iface, "l2tp", profile.server, "1701", profile.l2tpSecret,
544 "name", profile.username, "password", profile.password,
545 "linkname", "vpn", "refuse-eap", "nodefaultroute",
546 "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
547 };
548 break;
549 }
550
551 VpnConfig config = new VpnConfig();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700552 config.legacy = true;
Jeff Sharkey82f85212012-08-24 11:17:25 -0700553 config.user = profile.key;
554 config.interfaze = iface;
555 config.session = profile.name;
556 config.routes = profile.routes;
557 if (!profile.dnsServers.isEmpty()) {
558 config.dnsServers = Arrays.asList(profile.dnsServers.split(" +"));
559 }
560 if (!profile.searchDomains.isEmpty()) {
561 config.searchDomains = Arrays.asList(profile.searchDomains.split(" +"));
562 }
563
564 startLegacyVpn(config, racoon, mtpd);
565 }
566
567 private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
568 stopLegacyVpn();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700569
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700570 // Prepare for the new request. This also checks the caller.
571 prepare(null, VpnConfig.LEGACY_VPN);
Jeff Sharkey899223b2012-08-04 15:24:58 -0700572 updateState(DetailedState.CONNECTING, "startLegacyVpn");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700573
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700574 // Start a new LegacyVpnRunner and we are done!
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700575 mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd);
576 mLegacyVpnRunner.start();
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700577 }
578
Jeff Sharkey899223b2012-08-04 15:24:58 -0700579 public synchronized void stopLegacyVpn() {
580 if (mLegacyVpnRunner != null) {
581 mLegacyVpnRunner.exit();
582 mLegacyVpnRunner = null;
583
584 synchronized (LegacyVpnRunner.TAG) {
585 // wait for old thread to completely finish before spinning up
586 // new instance, otherwise state updates can be out of order.
587 }
588 }
589 }
590
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700591 /**
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700592 * Return the information of the current ongoing legacy VPN.
593 */
594 public synchronized LegacyVpnInfo getLegacyVpnInfo() {
Chia-chi Yehdadc8572012-06-08 13:05:58 -0700595 // Check if the caller is authorized.
596 enforceControlPermission();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700597 if (mLegacyVpnRunner == null) return null;
598
599 final LegacyVpnInfo info = new LegacyVpnInfo();
600 info.key = mLegacyVpnRunner.mConfig.user;
601 info.state = LegacyVpnInfo.stateFromNetworkInfo(mNetworkInfo);
602 if (mNetworkInfo.isConnected()) {
603 info.intent = mStatusIntent;
604 }
605 return info;
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700606 }
607
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700608 public VpnConfig getLegacyVpnConfig() {
609 if (mLegacyVpnRunner != null) {
610 return mLegacyVpnRunner.mConfig;
611 } else {
612 return null;
613 }
614 }
615
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700616 /**
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700617 * Bringing up a VPN connection takes time, and that is all this thread
618 * does. Here we have plenty of time. The only thing we need to take
619 * care of is responding to interruptions as soon as possible. Otherwise
620 * requests will be piled up. This can be done in a Handler as a state
621 * machine, but it is much easier to read in the current form.
622 */
623 private class LegacyVpnRunner extends Thread {
624 private static final String TAG = "LegacyVpnRunner";
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700625
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700626 private final VpnConfig mConfig;
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700627 private final String[] mDaemons;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700628 private final String[][] mArguments;
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700629 private final LocalSocket[] mSockets;
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700630
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700631 private long mTimer = -1;
632
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700633 public LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700634 super(TAG);
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700635 mConfig = config;
636 mDaemons = new String[] {"racoon", "mtpd"};
Jeff Sharkey899223b2012-08-04 15:24:58 -0700637 // TODO: clear arguments from memory once launched
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700638 mArguments = new String[][] {racoon, mtpd};
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700639 mSockets = new LocalSocket[mDaemons.length];
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700640 }
641
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700642 public void check(String interfaze) {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700643 if (interfaze.equals(mConfig.interfaze)) {
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700644 Log.i(TAG, "Legacy VPN is going down with " + interfaze);
645 exit();
646 }
647 }
648
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700649 public void exit() {
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700650 // We assume that everything is reset after stopping the daemons.
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700651 interrupt();
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700652 for (LocalSocket socket : mSockets) {
Jeff Sharkey065b2992012-08-05 14:16:48 -0700653 IoUtils.closeQuietly(socket);
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700654 }
Jeff Sharkey899223b2012-08-04 15:24:58 -0700655 updateState(DetailedState.DISCONNECTED, "exit");
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700656 }
657
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700658 @Override
659 public void run() {
660 // Wait for the previous thread since it has been interrupted.
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700661 Log.v(TAG, "Waiting");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700662 synchronized (TAG) {
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700663 Log.v(TAG, "Executing");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700664 execute();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700665 monitorDaemons();
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700666 }
667 }
668
669 private void checkpoint(boolean yield) throws InterruptedException {
670 long now = SystemClock.elapsedRealtime();
671 if (mTimer == -1) {
672 mTimer = now;
673 Thread.sleep(1);
Chia-chi Yeh7ef86112011-07-22 15:46:52 -0700674 } else if (now - mTimer <= 60000) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700675 Thread.sleep(yield ? 200 : 1);
676 } else {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700677 updateState(DetailedState.FAILED, "checkpoint");
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700678 throw new IllegalStateException("Time is up");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700679 }
680 }
681
682 private void execute() {
683 // Catch all exceptions so we can clean up few things.
Jeff Sharkey899223b2012-08-04 15:24:58 -0700684 boolean initFinished = false;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700685 try {
686 // Initialize the timer.
687 checkpoint(false);
688
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700689 // Wait for the daemons to stop.
690 for (String daemon : mDaemons) {
Jeff Sharkey088f29f2012-08-05 14:55:04 -0700691 while (!SystemService.isStopped(daemon)) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700692 checkpoint(true);
693 }
694 }
695
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700696 // Clear the previous state.
697 File state = new File("/data/misc/vpn/state");
698 state.delete();
699 if (state.exists()) {
700 throw new IllegalStateException("Cannot delete the state");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700701 }
Chia-chi Yehc1872732011-12-08 16:51:41 -0800702 new File("/data/misc/vpn/abort").delete();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700703 initFinished = true;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700704
Chia-chi Yehe9107902011-07-02 01:48:50 -0700705 // Check if we need to restart any of the daemons.
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700706 boolean restart = false;
707 for (String[] arguments : mArguments) {
708 restart = restart || (arguments != null);
709 }
710 if (!restart) {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700711 updateState(DetailedState.DISCONNECTED, "execute");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700712 return;
713 }
Jeff Sharkey899223b2012-08-04 15:24:58 -0700714 updateState(DetailedState.CONNECTING, "execute");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700715
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700716 // Start the daemon with arguments.
717 for (int i = 0; i < mDaemons.length; ++i) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700718 String[] arguments = mArguments[i];
719 if (arguments == null) {
720 continue;
721 }
722
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700723 // Start the daemon.
724 String daemon = mDaemons[i];
Jeff Sharkey088f29f2012-08-05 14:55:04 -0700725 SystemService.start(daemon);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700726
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700727 // Wait for the daemon to start.
Jeff Sharkey088f29f2012-08-05 14:55:04 -0700728 while (!SystemService.isRunning(daemon)) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700729 checkpoint(true);
730 }
731
732 // Create the control socket.
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700733 mSockets[i] = new LocalSocket();
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700734 LocalSocketAddress address = new LocalSocketAddress(
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700735 daemon, LocalSocketAddress.Namespace.RESERVED);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700736
737 // Wait for the socket to connect.
738 while (true) {
739 try {
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700740 mSockets[i].connect(address);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700741 break;
742 } catch (Exception e) {
743 // ignore
744 }
745 checkpoint(true);
746 }
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700747 mSockets[i].setSoTimeout(500);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700748
749 // Send over the arguments.
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700750 OutputStream out = mSockets[i].getOutputStream();
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700751 for (String argument : arguments) {
752 byte[] bytes = argument.getBytes(Charsets.UTF_8);
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700753 if (bytes.length >= 0xFFFF) {
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700754 throw new IllegalArgumentException("Argument is too large");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700755 }
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700756 out.write(bytes.length >> 8);
757 out.write(bytes.length);
758 out.write(bytes);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700759 checkpoint(false);
760 }
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700761 out.write(0xFF);
762 out.write(0xFF);
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700763 out.flush();
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700764
765 // Wait for End-of-File.
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700766 InputStream in = mSockets[i].getInputStream();
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700767 while (true) {
768 try {
769 if (in.read() == -1) {
770 break;
771 }
772 } catch (Exception e) {
773 // ignore
774 }
775 checkpoint(true);
776 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700777 }
778
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700779 // Wait for the daemons to create the new state.
780 while (!state.exists()) {
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700781 // Check if a running daemon is dead.
782 for (int i = 0; i < mDaemons.length; ++i) {
783 String daemon = mDaemons[i];
Jeff Sharkey088f29f2012-08-05 14:55:04 -0700784 if (mArguments[i] != null && !SystemService.isRunning(daemon)) {
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700785 throw new IllegalStateException(daemon + " is dead");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700786 }
787 }
788 checkpoint(true);
789 }
790
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700791 // Now we are connected. Read and parse the new state.
Chia-chi Yehc1bac3a2011-12-16 15:00:31 -0800792 String[] parameters = FileUtils.readTextFile(state, 0, null).split("\n", -1);
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700793 if (parameters.length != 6) {
794 throw new IllegalStateException("Cannot parse the state");
795 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700796
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700797 // Set the interface and the addresses in the config.
798 mConfig.interfaze = parameters[0].trim();
799 mConfig.addresses = parameters[1].trim();
800
801 // Set the routes if they are not set in the config.
802 if (mConfig.routes == null || mConfig.routes.isEmpty()) {
803 mConfig.routes = parameters[2].trim();
804 }
805
806 // Set the DNS servers if they are not set in the config.
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700807 if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700808 String dnsServers = parameters[3].trim();
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700809 if (!dnsServers.isEmpty()) {
810 mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
811 }
812 }
813
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700814 // Set the search domains if they are not set in the config.
815 if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) {
816 String searchDomains = parameters[4].trim();
817 if (!searchDomains.isEmpty()) {
818 mConfig.searchDomains = Arrays.asList(searchDomains.split(" "));
819 }
820 }
Chia-chi Yehe9107902011-07-02 01:48:50 -0700821
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700822 // Set the routes.
823 jniSetRoutes(mConfig.interfaze, mConfig.routes);
824
825 // Here is the last step and it must be done synchronously.
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700826 synchronized (Vpn.this) {
827 // Check if the thread is interrupted while we are waiting.
828 checkpoint(false);
829
Chia-chi Yehe9107902011-07-02 01:48:50 -0700830 // Check if the interface is gone while we are waiting.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700831 if (jniCheck(mConfig.interfaze) == 0) {
Chia-chi Yeh34e78132011-07-03 03:07:07 -0700832 throw new IllegalStateException(mConfig.interfaze + " is gone");
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700833 }
Chia-chi Yehe9107902011-07-02 01:48:50 -0700834
835 // Now INetworkManagementEventObserver is watching our back.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700836 mInterface = mConfig.interfaze;
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700837 mCallback.override(mConfig.dnsServers, mConfig.searchDomains);
838 showNotification(mConfig, null, null);
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700839
840 Log.i(TAG, "Connected!");
Jeff Sharkey899223b2012-08-04 15:24:58 -0700841 updateState(DetailedState.CONNECTED, "execute");
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700842 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700843 } catch (Exception e) {
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700844 Log.i(TAG, "Aborting", e);
Chia-chi Yehe9107902011-07-02 01:48:50 -0700845 exit();
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700846 } finally {
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700847 // Kill the daemons if they fail to stop.
Jeff Sharkey899223b2012-08-04 15:24:58 -0700848 if (!initFinished) {
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700849 for (String daemon : mDaemons) {
Jeff Sharkey088f29f2012-08-05 14:55:04 -0700850 SystemService.stop(daemon);
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700851 }
852 }
853
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700854 // Do not leave an unstable state.
Jeff Sharkey899223b2012-08-04 15:24:58 -0700855 if (!initFinished || mNetworkInfo.getDetailedState() == DetailedState.CONNECTING) {
856 updateState(DetailedState.FAILED, "execute");
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700857 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700858 }
859 }
Jeff Sharkey899223b2012-08-04 15:24:58 -0700860
861 /**
862 * Monitor the daemons we started, moving to disconnected state if the
863 * underlying services fail.
864 */
865 private void monitorDaemons() {
866 if (!mNetworkInfo.isConnected()) {
867 return;
868 }
869
870 try {
871 while (true) {
872 Thread.sleep(2000);
873 for (int i = 0; i < mDaemons.length; i++) {
874 if (mArguments[i] != null && SystemService.isStopped(mDaemons[i])) {
875 return;
876 }
877 }
878 }
879 } catch (InterruptedException e) {
880 Log.d(TAG, "interrupted during monitorDaemons(); stopping services");
881 } finally {
882 for (String daemon : mDaemons) {
883 SystemService.stop(daemon);
884 }
885
886 updateState(DetailedState.DISCONNECTED, "babysit");
887 }
888 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700889 }
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700890}