blob: 03ff21f0645040337a4c9f1c830f3396ce23f917 [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;
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -070053import android.os.UserHandle;
Jeff Sharkey82f85212012-08-24 11:17:25 -070054import android.security.Credentials;
55import android.security.KeyStore;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070056import android.util.Log;
Jeff Sharkey82f85212012-08-24 11:17:25 -070057import android.widget.Toast;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070058
59import com.android.internal.R;
Chia-chi Yeh2e467642011-07-04 03:23:12 -070060import com.android.internal.net.LegacyVpnInfo;
Chia-chi Yeh04ba25c2011-06-15 17:07:27 -070061import com.android.internal.net.VpnConfig;
Jeff Sharkey82f85212012-08-24 11:17:25 -070062import com.android.internal.net.VpnProfile;
Jeff Sharkey899223b2012-08-04 15:24:58 -070063import com.android.internal.util.Preconditions;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070064import com.android.server.ConnectivityService.VpnCallback;
Jeff Sharkey899223b2012-08-04 15:24:58 -070065import com.android.server.net.BaseNetworkObserver;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070066
Chia-chi Yeh97a61562011-07-14 15:05:05 -070067import java.io.File;
Chia-chi Yeh97a61562011-07-14 15:05:05 -070068import java.io.InputStream;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070069import java.io.OutputStream;
Jeff Sharkey82f85212012-08-24 11:17:25 -070070import java.net.Inet4Address;
71import java.net.InetAddress;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070072import java.nio.charset.Charsets;
Chia-chi Yeh41d16852011-07-01 02:12:06 -070073import java.util.Arrays;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070074
Jeff Sharkey065b2992012-08-05 14:16:48 -070075import libcore.io.IoUtils;
76
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070077/**
78 * @hide
79 */
Jeff Sharkey899223b2012-08-04 15:24:58 -070080public class Vpn extends BaseNetworkStateTracker {
81 private static final String TAG = "Vpn";
82 private static final boolean LOGD = true;
83
84 // TODO: create separate trackers for each unique VPN to support
85 // automated reconnection
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070086
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070087 private final VpnCallback mCallback;
88
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -070089 private String mPackage = VpnConfig.LEGACY_VPN;
90 private String mInterface;
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -070091 private Connection mConnection;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070092 private LegacyVpnRunner mLegacyVpnRunner;
Jeff Sharkey899223b2012-08-04 15:24:58 -070093 private PendingIntent mStatusIntent;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070094 private boolean mEnableNotif = true;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070095
Jeff Sharkey899223b2012-08-04 15:24:58 -070096 public Vpn(Context context, VpnCallback callback, INetworkManagementService netService) {
97 // TODO: create dedicated TYPE_VPN network type
98 super(ConnectivityManager.TYPE_DUMMY);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070099 mContext = context;
100 mCallback = callback;
Jeff Sharkey899223b2012-08-04 15:24:58 -0700101
102 try {
103 netService.registerObserver(mObserver);
104 } catch (RemoteException e) {
105 Log.wtf(TAG, "Problem registering observer", e);
106 }
107 }
108
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700109 public void setEnableNotifications(boolean enableNotif) {
110 mEnableNotif = enableNotif;
111 }
112
Jeff Sharkey899223b2012-08-04 15:24:58 -0700113 @Override
114 protected void startMonitoringInternal() {
115 // Ignored; events are sent through callbacks for now
116 }
117
118 @Override
119 public boolean teardown() {
120 // TODO: finish migration to unique tracker for each VPN
121 throw new UnsupportedOperationException();
122 }
123
124 @Override
125 public boolean reconnect() {
126 // TODO: finish migration to unique tracker for each VPN
127 throw new UnsupportedOperationException();
128 }
129
130 @Override
131 public String getTcpBufferSizesPropName() {
132 return PROP_TCP_BUFFER_UNKNOWN;
133 }
134
135 /**
136 * Update current state, dispaching event to listeners.
137 */
138 private void updateState(DetailedState detailedState, String reason) {
139 if (LOGD) Log.d(TAG, "setting state=" + detailedState + ", reason=" + reason);
140 mNetworkInfo.setDetailedState(detailedState, reason, null);
141 mCallback.onStateChanged(new NetworkInfo(mNetworkInfo));
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700142 }
143
144 /**
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700145 * Prepare for a VPN application. This method is designed to solve
146 * race conditions. It first compares the current prepared package
147 * with {@code oldPackage}. If they are the same, the prepared
148 * package is revoked and replaced with {@code newPackage}. If
149 * {@code oldPackage} is {@code null}, the comparison is omitted.
150 * If {@code newPackage} is the same package or {@code null}, the
151 * revocation is omitted. This method returns {@code true} if the
152 * operation is succeeded.
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700153 *
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700154 * Legacy VPN is handled specially since it is not a real package.
155 * It uses {@link VpnConfig#LEGACY_VPN} as its package name, and
156 * it can be revoked by itself.
157 *
158 * @param oldPackage The package name of the old VPN application.
159 * @param newPackage The package name of the new VPN application.
160 * @return true if the operation is succeeded.
Chia-chi Yehe9107902011-07-02 01:48:50 -0700161 */
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700162 public synchronized boolean prepare(String oldPackage, String newPackage) {
163 // Return false if the package does not match.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700164 if (oldPackage != null && !oldPackage.equals(mPackage)) {
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700165 return false;
Chia-chi Yehe9107902011-07-02 01:48:50 -0700166 }
167
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700168 // Return true if we do not need to revoke.
169 if (newPackage == null ||
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700170 (newPackage.equals(mPackage) && !newPackage.equals(VpnConfig.LEGACY_VPN))) {
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700171 return true;
172 }
173
Chia-chi Yehdadc8572012-06-08 13:05:58 -0700174 // Check if the caller is authorized.
175 enforceControlPermission();
Chia-chi Yehe9107902011-07-02 01:48:50 -0700176
Chia-chi Yehe9107902011-07-02 01:48:50 -0700177 // Reset the interface and hide the notification.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700178 if (mInterface != null) {
179 jniReset(mInterface);
Jeff Sharkey899223b2012-08-04 15:24:58 -0700180 final long token = Binder.clearCallingIdentity();
181 try {
182 mCallback.restore();
183 hideNotification();
184 } finally {
185 Binder.restoreCallingIdentity(token);
186 }
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700187 mInterface = null;
Chia-chi Yehe9107902011-07-02 01:48:50 -0700188 }
189
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700190 // Revoke the connection or stop LegacyVpnRunner.
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700191 if (mConnection != null) {
192 try {
193 mConnection.mService.transact(IBinder.LAST_CALL_TRANSACTION,
194 Parcel.obtain(), null, IBinder.FLAG_ONEWAY);
195 } catch (Exception e) {
196 // ignore
197 }
198 mContext.unbindService(mConnection);
199 mConnection = null;
Chia-chi Yehe9107902011-07-02 01:48:50 -0700200 } else if (mLegacyVpnRunner != null) {
201 mLegacyVpnRunner.exit();
202 mLegacyVpnRunner = null;
203 }
204
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700205 Log.i(TAG, "Switched from " + mPackage + " to " + newPackage);
206 mPackage = newPackage;
Jeff Sharkey899223b2012-08-04 15:24:58 -0700207 updateState(DetailedState.IDLE, "prepare");
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700208 return true;
Chia-chi Yehe9107902011-07-02 01:48:50 -0700209 }
210
211 /**
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700212 * Protect a socket from routing changes by binding it to the given
213 * interface. The socket is NOT closed by this method.
214 *
215 * @param socket The socket to be bound.
Jeff Sharkey899223b2012-08-04 15:24:58 -0700216 * @param interfaze The name of the interface.
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700217 */
218 public void protect(ParcelFileDescriptor socket, String interfaze) throws Exception {
219 PackageManager pm = mContext.getPackageManager();
220 ApplicationInfo app = pm.getApplicationInfo(mPackage, 0);
221 if (Binder.getCallingUid() != app.uid) {
222 throw new SecurityException("Unauthorized Caller");
223 }
224 jniProtect(socket.getFd(), interfaze);
225 }
226
227 /**
Chia-chi Yehe9107902011-07-02 01:48:50 -0700228 * Establish a VPN network and return the file descriptor of the VPN
229 * interface. This methods returns {@code null} if the application is
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700230 * revoked or not prepared.
Chia-chi Yehe9107902011-07-02 01:48:50 -0700231 *
232 * @param config The parameters to configure the network.
233 * @return The file descriptor of the VPN interface.
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700234 */
Chia-chi Yeh04ba25c2011-06-15 17:07:27 -0700235 public synchronized ParcelFileDescriptor establish(VpnConfig config) {
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700236 // Check if the caller is already prepared.
237 PackageManager pm = mContext.getPackageManager();
238 ApplicationInfo app = null;
239 try {
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700240 app = pm.getApplicationInfo(mPackage, 0);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700241 } catch (Exception e) {
Chia-chi Yeh7b0b8342011-06-17 14:34:11 -0700242 return null;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700243 }
244 if (Binder.getCallingUid() != app.uid) {
Chia-chi Yeh7b0b8342011-06-17 14:34:11 -0700245 return null;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700246 }
247
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700248 // Check if the service is properly declared.
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700249 Intent intent = new Intent(VpnConfig.SERVICE_INTERFACE);
250 intent.setClassName(mPackage, config.user);
251 ResolveInfo info = pm.resolveService(intent, 0);
252 if (info == null) {
253 throw new SecurityException("Cannot find " + config.user);
254 }
255 if (!BIND_VPN_SERVICE.equals(info.serviceInfo.permission)) {
256 throw new SecurityException(config.user + " does not require " + BIND_VPN_SERVICE);
257 }
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700258
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700259 // Load the label.
260 String label = app.loadLabel(pm).toString();
261
262 // Load the icon and convert it into a bitmap.
263 Drawable icon = app.loadIcon(pm);
264 Bitmap bitmap = null;
265 if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) {
266 int width = mContext.getResources().getDimensionPixelSize(
267 android.R.dimen.notification_large_icon_width);
268 int height = mContext.getResources().getDimensionPixelSize(
269 android.R.dimen.notification_large_icon_height);
270 icon.setBounds(0, 0, width, height);
271 bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Dianne Hackborn6311d0a2011-08-02 16:37:58 -0700272 Canvas c = new Canvas(bitmap);
273 icon.draw(c);
274 c.setBitmap(null);
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700275 }
276
Chia-chi Yehe9107902011-07-02 01:48:50 -0700277 // Configure the interface. Abort if any of these steps fails.
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700278 ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu));
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700279 try {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700280 updateState(DetailedState.CONNECTING, "establish");
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700281 String interfaze = jniGetName(tun.getFd());
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700282 if (jniSetAddresses(interfaze, config.addresses) < 1) {
283 throw new IllegalArgumentException("At least one address must be specified");
284 }
285 if (config.routes != null) {
286 jniSetRoutes(interfaze, config.routes);
287 }
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700288 Connection connection = new Connection();
289 if (!mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
290 throw new IllegalStateException("Cannot bind " + config.user);
291 }
292 if (mConnection != null) {
293 mContext.unbindService(mConnection);
294 }
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700295 if (mInterface != null && !mInterface.equals(interfaze)) {
296 jniReset(mInterface);
Chia-chi Yehf4e3bf82011-06-30 12:33:17 -0700297 }
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700298 mConnection = connection;
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700299 mInterface = interfaze;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700300 } catch (RuntimeException e) {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700301 updateState(DetailedState.FAILED, "establish");
Jeff Sharkey065b2992012-08-05 14:16:48 -0700302 IoUtils.closeQuietly(tun);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700303 throw e;
304 }
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700305 Log.i(TAG, "Established by " + config.user + " on " + mInterface);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700306
Chia-chi Yeh8909b102011-07-01 01:09:42 -0700307 // Fill more values.
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700308 config.user = mPackage;
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700309 config.interfaze = mInterface;
Chia-chi Yeh8909b102011-07-01 01:09:42 -0700310
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700311 // Override DNS servers and show the notification.
Jeff Sharkey899223b2012-08-04 15:24:58 -0700312 final long token = Binder.clearCallingIdentity();
313 try {
314 mCallback.override(config.dnsServers, config.searchDomains);
315 showNotification(config, label, bitmap);
316 } finally {
317 Binder.restoreCallingIdentity(token);
318 }
319 // TODO: ensure that contract class eventually marks as connected
320 updateState(DetailedState.AUTHENTICATING, "establish");
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700321 return tun;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700322 }
323
Jeff Sharkey899223b2012-08-04 15:24:58 -0700324 @Deprecated
325 public synchronized void interfaceStatusChanged(String iface, boolean up) {
326 try {
327 mObserver.interfaceStatusChanged(iface, up);
328 } catch (RemoteException e) {
329 // ignored; target is local
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700330 }
331 }
332
Jeff Sharkey899223b2012-08-04 15:24:58 -0700333 private INetworkManagementEventObserver mObserver = new BaseNetworkObserver() {
334 @Override
335 public void interfaceStatusChanged(String interfaze, boolean up) {
336 synchronized (Vpn.this) {
337 if (!up && mLegacyVpnRunner != null) {
338 mLegacyVpnRunner.check(interfaze);
339 }
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700340 }
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700341 }
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700342
Jeff Sharkey899223b2012-08-04 15:24:58 -0700343 @Override
344 public void interfaceRemoved(String interfaze) {
345 synchronized (Vpn.this) {
346 if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) {
347 final long token = Binder.clearCallingIdentity();
348 try {
349 mCallback.restore();
350 hideNotification();
351 } finally {
352 Binder.restoreCallingIdentity(token);
353 }
354 mInterface = null;
355 if (mConnection != null) {
356 mContext.unbindService(mConnection);
357 mConnection = null;
358 updateState(DetailedState.DISCONNECTED, "interfaceRemoved");
359 } else if (mLegacyVpnRunner != null) {
360 mLegacyVpnRunner.exit();
361 mLegacyVpnRunner = null;
362 }
363 }
364 }
365 }
366 };
Haoyu Baidb3c8672012-06-20 14:29:57 -0700367
Chia-chi Yehdadc8572012-06-08 13:05:58 -0700368 private void enforceControlPermission() {
369 // System user is allowed to control VPN.
370 if (Binder.getCallingUid() == Process.SYSTEM_UID) {
371 return;
372 }
373
374 try {
375 // System dialogs are also allowed to control VPN.
376 PackageManager pm = mContext.getPackageManager();
377 ApplicationInfo app = pm.getApplicationInfo(VpnConfig.DIALOGS_PACKAGE, 0);
378 if (Binder.getCallingUid() == app.uid) {
379 return;
380 }
381 } catch (Exception e) {
382 // ignore
383 }
384
385 throw new SecurityException("Unauthorized Caller");
386 }
387
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700388 private class Connection implements ServiceConnection {
389 private IBinder mService;
390
391 @Override
392 public void onServiceConnected(ComponentName name, IBinder service) {
393 mService = service;
394 }
395
396 @Override
397 public void onServiceDisconnected(ComponentName name) {
398 mService = null;
399 }
400 }
401
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700402 private void showNotification(VpnConfig config, String label, Bitmap icon) {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700403 if (!mEnableNotif) return;
Jeff Sharkey899223b2012-08-04 15:24:58 -0700404 mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext, config);
405
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700406 NotificationManager nm = (NotificationManager)
407 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
408
409 if (nm != null) {
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700410 String title = (label == null) ? mContext.getString(R.string.vpn_title) :
411 mContext.getString(R.string.vpn_title_long, label);
Chia-chi Yeh34e78132011-07-03 03:07:07 -0700412 String text = (config.session == null) ? mContext.getString(R.string.vpn_text) :
413 mContext.getString(R.string.vpn_text_long, config.session);
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700414 config.startTime = SystemClock.elapsedRealtime();
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700415
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700416 Notification notification = new Notification.Builder(mContext)
417 .setSmallIcon(R.drawable.vpn_connected)
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700418 .setLargeIcon(icon)
419 .setContentTitle(title)
Chia-chi Yehf8905fd2011-06-14 16:35:02 -0700420 .setContentText(text)
Jeff Sharkey899223b2012-08-04 15:24:58 -0700421 .setContentIntent(mStatusIntent)
Chia-chi Yeh50fe7092012-01-11 14:26:24 -0800422 .setDefaults(0)
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700423 .setOngoing(true)
Jeff Sharkey899223b2012-08-04 15:24:58 -0700424 .build();
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700425 nm.notifyAsUser(null, R.drawable.vpn_connected, notification, UserHandle.ALL);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700426 }
427 }
428
429 private void hideNotification() {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700430 if (!mEnableNotif) return;
Jeff Sharkey899223b2012-08-04 15:24:58 -0700431 mStatusIntent = null;
432
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700433 NotificationManager nm = (NotificationManager)
434 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
435
436 if (nm != null) {
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700437 nm.cancelAsUser(null, R.drawable.vpn_connected, UserHandle.ALL);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700438 }
439 }
440
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700441 private native int jniCreate(int mtu);
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700442 private native String jniGetName(int tun);
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700443 private native int jniSetAddresses(String interfaze, String addresses);
444 private native int jniSetRoutes(String interfaze, String routes);
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700445 private native void jniReset(String interfaze);
446 private native int jniCheck(String interfaze);
447 private native void jniProtect(int socket, String interfaze);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700448
Jeff Sharkey82f85212012-08-24 11:17:25 -0700449 private static String findLegacyVpnGateway(LinkProperties prop) {
450 for (RouteInfo route : prop.getRoutes()) {
451 // Currently legacy VPN only works on IPv4.
452 if (route.isDefaultRoute() && route.getGateway() instanceof Inet4Address) {
453 return route.getGateway().getHostAddress();
454 }
455 }
Jeff Sharkey899223b2012-08-04 15:24:58 -0700456
Jeff Sharkey82f85212012-08-24 11:17:25 -0700457 throw new IllegalStateException("Unable to find suitable gateway");
458 }
459
460 /**
461 * Start legacy VPN, controlling native daemons as needed. Creates a
462 * secondary thread to perform connection work, returning quickly.
463 */
464 public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, LinkProperties egress) {
465 if (keyStore.state() != KeyStore.State.UNLOCKED) {
466 throw new IllegalStateException("KeyStore isn't unlocked");
467 }
468
469 final String iface = egress.getInterfaceName();
470 final String gateway = findLegacyVpnGateway(egress);
471
472 // Load certificates.
473 String privateKey = "";
474 String userCert = "";
475 String caCert = "";
476 String serverCert = "";
477 if (!profile.ipsecUserCert.isEmpty()) {
478 privateKey = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert;
479 byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert);
480 userCert = (value == null) ? null : new String(value, Charsets.UTF_8);
481 }
482 if (!profile.ipsecCaCert.isEmpty()) {
483 byte[] value = keyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert);
484 caCert = (value == null) ? null : new String(value, Charsets.UTF_8);
485 }
486 if (!profile.ipsecServerCert.isEmpty()) {
487 byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecServerCert);
488 serverCert = (value == null) ? null : new String(value, Charsets.UTF_8);
489 }
490 if (privateKey == null || userCert == null || caCert == null || serverCert == null) {
491 throw new IllegalStateException("Cannot load credentials");
492 }
493
494 // Prepare arguments for racoon.
495 String[] racoon = null;
496 switch (profile.type) {
497 case VpnProfile.TYPE_L2TP_IPSEC_PSK:
498 racoon = new String[] {
499 iface, profile.server, "udppsk", profile.ipsecIdentifier,
500 profile.ipsecSecret, "1701",
501 };
502 break;
503 case VpnProfile.TYPE_L2TP_IPSEC_RSA:
504 racoon = new String[] {
505 iface, profile.server, "udprsa", privateKey, userCert,
506 caCert, serverCert, "1701",
507 };
508 break;
509 case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
510 racoon = new String[] {
511 iface, profile.server, "xauthpsk", profile.ipsecIdentifier,
512 profile.ipsecSecret, profile.username, profile.password, "", gateway,
513 };
514 break;
515 case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
516 racoon = new String[] {
517 iface, profile.server, "xauthrsa", privateKey, userCert,
518 caCert, serverCert, profile.username, profile.password, "", gateway,
519 };
520 break;
521 case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
522 racoon = new String[] {
523 iface, profile.server, "hybridrsa",
524 caCert, serverCert, profile.username, profile.password, "", gateway,
525 };
526 break;
527 }
528
529 // Prepare arguments for mtpd.
530 String[] mtpd = null;
531 switch (profile.type) {
532 case VpnProfile.TYPE_PPTP:
533 mtpd = new String[] {
534 iface, "pptp", profile.server, "1723",
535 "name", profile.username, "password", profile.password,
536 "linkname", "vpn", "refuse-eap", "nodefaultroute",
537 "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
538 (profile.mppe ? "+mppe" : "nomppe"),
539 };
540 break;
541 case VpnProfile.TYPE_L2TP_IPSEC_PSK:
542 case VpnProfile.TYPE_L2TP_IPSEC_RSA:
543 mtpd = new String[] {
544 iface, "l2tp", profile.server, "1701", profile.l2tpSecret,
545 "name", profile.username, "password", profile.password,
546 "linkname", "vpn", "refuse-eap", "nodefaultroute",
547 "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
548 };
549 break;
550 }
551
552 VpnConfig config = new VpnConfig();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700553 config.legacy = true;
Jeff Sharkey82f85212012-08-24 11:17:25 -0700554 config.user = profile.key;
555 config.interfaze = iface;
556 config.session = profile.name;
557 config.routes = profile.routes;
558 if (!profile.dnsServers.isEmpty()) {
559 config.dnsServers = Arrays.asList(profile.dnsServers.split(" +"));
560 }
561 if (!profile.searchDomains.isEmpty()) {
562 config.searchDomains = Arrays.asList(profile.searchDomains.split(" +"));
563 }
564
565 startLegacyVpn(config, racoon, mtpd);
566 }
567
568 private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
569 stopLegacyVpn();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700570
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700571 // Prepare for the new request. This also checks the caller.
572 prepare(null, VpnConfig.LEGACY_VPN);
Jeff Sharkey899223b2012-08-04 15:24:58 -0700573 updateState(DetailedState.CONNECTING, "startLegacyVpn");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700574
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700575 // Start a new LegacyVpnRunner and we are done!
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700576 mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd);
577 mLegacyVpnRunner.start();
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700578 }
579
Jeff Sharkey899223b2012-08-04 15:24:58 -0700580 public synchronized void stopLegacyVpn() {
581 if (mLegacyVpnRunner != null) {
582 mLegacyVpnRunner.exit();
583 mLegacyVpnRunner = null;
584
585 synchronized (LegacyVpnRunner.TAG) {
586 // wait for old thread to completely finish before spinning up
587 // new instance, otherwise state updates can be out of order.
588 }
589 }
590 }
591
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700592 /**
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700593 * Return the information of the current ongoing legacy VPN.
594 */
595 public synchronized LegacyVpnInfo getLegacyVpnInfo() {
Chia-chi Yehdadc8572012-06-08 13:05:58 -0700596 // Check if the caller is authorized.
597 enforceControlPermission();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700598 if (mLegacyVpnRunner == null) return null;
599
600 final LegacyVpnInfo info = new LegacyVpnInfo();
601 info.key = mLegacyVpnRunner.mConfig.user;
602 info.state = LegacyVpnInfo.stateFromNetworkInfo(mNetworkInfo);
603 if (mNetworkInfo.isConnected()) {
604 info.intent = mStatusIntent;
605 }
606 return info;
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700607 }
608
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700609 public VpnConfig getLegacyVpnConfig() {
610 if (mLegacyVpnRunner != null) {
611 return mLegacyVpnRunner.mConfig;
612 } else {
613 return null;
614 }
615 }
616
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700617 /**
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700618 * Bringing up a VPN connection takes time, and that is all this thread
619 * does. Here we have plenty of time. The only thing we need to take
620 * care of is responding to interruptions as soon as possible. Otherwise
621 * requests will be piled up. This can be done in a Handler as a state
622 * machine, but it is much easier to read in the current form.
623 */
624 private class LegacyVpnRunner extends Thread {
625 private static final String TAG = "LegacyVpnRunner";
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700626
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700627 private final VpnConfig mConfig;
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700628 private final String[] mDaemons;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700629 private final String[][] mArguments;
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700630 private final LocalSocket[] mSockets;
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700631
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700632 private long mTimer = -1;
633
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700634 public LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700635 super(TAG);
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700636 mConfig = config;
637 mDaemons = new String[] {"racoon", "mtpd"};
Jeff Sharkey899223b2012-08-04 15:24:58 -0700638 // TODO: clear arguments from memory once launched
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700639 mArguments = new String[][] {racoon, mtpd};
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700640 mSockets = new LocalSocket[mDaemons.length];
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700641 }
642
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700643 public void check(String interfaze) {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700644 if (interfaze.equals(mConfig.interfaze)) {
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700645 Log.i(TAG, "Legacy VPN is going down with " + interfaze);
646 exit();
647 }
648 }
649
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700650 public void exit() {
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700651 // We assume that everything is reset after stopping the daemons.
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700652 interrupt();
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700653 for (LocalSocket socket : mSockets) {
Jeff Sharkey065b2992012-08-05 14:16:48 -0700654 IoUtils.closeQuietly(socket);
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700655 }
Jeff Sharkey899223b2012-08-04 15:24:58 -0700656 updateState(DetailedState.DISCONNECTED, "exit");
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700657 }
658
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700659 @Override
660 public void run() {
661 // Wait for the previous thread since it has been interrupted.
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700662 Log.v(TAG, "Waiting");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700663 synchronized (TAG) {
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700664 Log.v(TAG, "Executing");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700665 execute();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700666 monitorDaemons();
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700667 }
668 }
669
670 private void checkpoint(boolean yield) throws InterruptedException {
671 long now = SystemClock.elapsedRealtime();
672 if (mTimer == -1) {
673 mTimer = now;
674 Thread.sleep(1);
Chia-chi Yeh7ef86112011-07-22 15:46:52 -0700675 } else if (now - mTimer <= 60000) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700676 Thread.sleep(yield ? 200 : 1);
677 } else {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700678 updateState(DetailedState.FAILED, "checkpoint");
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700679 throw new IllegalStateException("Time is up");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700680 }
681 }
682
683 private void execute() {
684 // Catch all exceptions so we can clean up few things.
Jeff Sharkey899223b2012-08-04 15:24:58 -0700685 boolean initFinished = false;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700686 try {
687 // Initialize the timer.
688 checkpoint(false);
689
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700690 // Wait for the daemons to stop.
691 for (String daemon : mDaemons) {
Jeff Sharkey088f29f2012-08-05 14:55:04 -0700692 while (!SystemService.isStopped(daemon)) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700693 checkpoint(true);
694 }
695 }
696
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700697 // Clear the previous state.
698 File state = new File("/data/misc/vpn/state");
699 state.delete();
700 if (state.exists()) {
701 throw new IllegalStateException("Cannot delete the state");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700702 }
Chia-chi Yehc1872732011-12-08 16:51:41 -0800703 new File("/data/misc/vpn/abort").delete();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700704 initFinished = true;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700705
Chia-chi Yehe9107902011-07-02 01:48:50 -0700706 // Check if we need to restart any of the daemons.
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700707 boolean restart = false;
708 for (String[] arguments : mArguments) {
709 restart = restart || (arguments != null);
710 }
711 if (!restart) {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700712 updateState(DetailedState.DISCONNECTED, "execute");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700713 return;
714 }
Jeff Sharkey899223b2012-08-04 15:24:58 -0700715 updateState(DetailedState.CONNECTING, "execute");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700716
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700717 // Start the daemon with arguments.
718 for (int i = 0; i < mDaemons.length; ++i) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700719 String[] arguments = mArguments[i];
720 if (arguments == null) {
721 continue;
722 }
723
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700724 // Start the daemon.
725 String daemon = mDaemons[i];
Jeff Sharkey088f29f2012-08-05 14:55:04 -0700726 SystemService.start(daemon);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700727
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700728 // Wait for the daemon to start.
Jeff Sharkey088f29f2012-08-05 14:55:04 -0700729 while (!SystemService.isRunning(daemon)) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700730 checkpoint(true);
731 }
732
733 // Create the control socket.
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700734 mSockets[i] = new LocalSocket();
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700735 LocalSocketAddress address = new LocalSocketAddress(
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700736 daemon, LocalSocketAddress.Namespace.RESERVED);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700737
738 // Wait for the socket to connect.
739 while (true) {
740 try {
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700741 mSockets[i].connect(address);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700742 break;
743 } catch (Exception e) {
744 // ignore
745 }
746 checkpoint(true);
747 }
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700748 mSockets[i].setSoTimeout(500);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700749
750 // Send over the arguments.
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700751 OutputStream out = mSockets[i].getOutputStream();
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700752 for (String argument : arguments) {
753 byte[] bytes = argument.getBytes(Charsets.UTF_8);
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700754 if (bytes.length >= 0xFFFF) {
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700755 throw new IllegalArgumentException("Argument is too large");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700756 }
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700757 out.write(bytes.length >> 8);
758 out.write(bytes.length);
759 out.write(bytes);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700760 checkpoint(false);
761 }
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700762 out.write(0xFF);
763 out.write(0xFF);
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700764 out.flush();
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700765
766 // Wait for End-of-File.
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700767 InputStream in = mSockets[i].getInputStream();
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700768 while (true) {
769 try {
770 if (in.read() == -1) {
771 break;
772 }
773 } catch (Exception e) {
774 // ignore
775 }
776 checkpoint(true);
777 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700778 }
779
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700780 // Wait for the daemons to create the new state.
781 while (!state.exists()) {
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700782 // Check if a running daemon is dead.
783 for (int i = 0; i < mDaemons.length; ++i) {
784 String daemon = mDaemons[i];
Jeff Sharkey088f29f2012-08-05 14:55:04 -0700785 if (mArguments[i] != null && !SystemService.isRunning(daemon)) {
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700786 throw new IllegalStateException(daemon + " is dead");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700787 }
788 }
789 checkpoint(true);
790 }
791
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700792 // Now we are connected. Read and parse the new state.
Chia-chi Yehc1bac3a2011-12-16 15:00:31 -0800793 String[] parameters = FileUtils.readTextFile(state, 0, null).split("\n", -1);
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700794 if (parameters.length != 6) {
795 throw new IllegalStateException("Cannot parse the state");
796 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700797
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700798 // Set the interface and the addresses in the config.
799 mConfig.interfaze = parameters[0].trim();
800 mConfig.addresses = parameters[1].trim();
801
802 // Set the routes if they are not set in the config.
803 if (mConfig.routes == null || mConfig.routes.isEmpty()) {
804 mConfig.routes = parameters[2].trim();
805 }
806
807 // Set the DNS servers if they are not set in the config.
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700808 if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700809 String dnsServers = parameters[3].trim();
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700810 if (!dnsServers.isEmpty()) {
811 mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
812 }
813 }
814
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700815 // Set the search domains if they are not set in the config.
816 if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) {
817 String searchDomains = parameters[4].trim();
818 if (!searchDomains.isEmpty()) {
819 mConfig.searchDomains = Arrays.asList(searchDomains.split(" "));
820 }
821 }
Chia-chi Yehe9107902011-07-02 01:48:50 -0700822
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700823 // Set the routes.
824 jniSetRoutes(mConfig.interfaze, mConfig.routes);
825
826 // Here is the last step and it must be done synchronously.
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700827 synchronized (Vpn.this) {
828 // Check if the thread is interrupted while we are waiting.
829 checkpoint(false);
830
Chia-chi Yehe9107902011-07-02 01:48:50 -0700831 // Check if the interface is gone while we are waiting.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700832 if (jniCheck(mConfig.interfaze) == 0) {
Chia-chi Yeh34e78132011-07-03 03:07:07 -0700833 throw new IllegalStateException(mConfig.interfaze + " is gone");
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700834 }
Chia-chi Yehe9107902011-07-02 01:48:50 -0700835
836 // Now INetworkManagementEventObserver is watching our back.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700837 mInterface = mConfig.interfaze;
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700838 mCallback.override(mConfig.dnsServers, mConfig.searchDomains);
839 showNotification(mConfig, null, null);
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700840
841 Log.i(TAG, "Connected!");
Jeff Sharkey899223b2012-08-04 15:24:58 -0700842 updateState(DetailedState.CONNECTED, "execute");
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700843 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700844 } catch (Exception e) {
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700845 Log.i(TAG, "Aborting", e);
Chia-chi Yehe9107902011-07-02 01:48:50 -0700846 exit();
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700847 } finally {
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700848 // Kill the daemons if they fail to stop.
Jeff Sharkey899223b2012-08-04 15:24:58 -0700849 if (!initFinished) {
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700850 for (String daemon : mDaemons) {
Jeff Sharkey088f29f2012-08-05 14:55:04 -0700851 SystemService.stop(daemon);
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700852 }
853 }
854
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700855 // Do not leave an unstable state.
Jeff Sharkey899223b2012-08-04 15:24:58 -0700856 if (!initFinished || mNetworkInfo.getDetailedState() == DetailedState.CONNECTING) {
857 updateState(DetailedState.FAILED, "execute");
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700858 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700859 }
860 }
Jeff Sharkey899223b2012-08-04 15:24:58 -0700861
862 /**
863 * Monitor the daemons we started, moving to disconnected state if the
864 * underlying services fail.
865 */
866 private void monitorDaemons() {
867 if (!mNetworkInfo.isConnected()) {
868 return;
869 }
870
871 try {
872 while (true) {
873 Thread.sleep(2000);
874 for (int i = 0; i < mDaemons.length; i++) {
875 if (mArguments[i] != null && SystemService.isStopped(mDaemons[i])) {
876 return;
877 }
878 }
879 }
880 } catch (InterruptedException e) {
881 Log.d(TAG, "interrupted during monitorDaemons(); stopping services");
882 } finally {
883 for (String daemon : mDaemons) {
884 SystemService.stop(daemon);
885 }
886
887 updateState(DetailedState.DISCONNECTED, "babysit");
888 }
889 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700890 }
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700891}