blob: 10c7e2776921d489c2c977003ab72daa7cc6b404 [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;
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -070024import android.content.BroadcastReceiver;
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -070025import android.content.ComponentName;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070026import android.content.Context;
27import android.content.Intent;
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -070028import android.content.IntentFilter;
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -070029import android.content.ServiceConnection;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070030import android.content.pm.ApplicationInfo;
31import android.content.pm.PackageManager;
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -070032import android.content.pm.ResolveInfo;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070033import android.graphics.Bitmap;
34import android.graphics.Canvas;
35import android.graphics.drawable.Drawable;
Jeff Sharkey899223b2012-08-04 15:24:58 -070036import android.net.BaseNetworkStateTracker;
37import android.net.ConnectivityManager;
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -070038import android.net.IConnectivityManager;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070039import android.net.INetworkManagementEventObserver;
Jeff Sharkey82f85212012-08-24 11:17:25 -070040import android.net.LinkProperties;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070041import android.net.LocalSocket;
42import android.net.LocalSocketAddress;
Jeff Sharkey899223b2012-08-04 15:24:58 -070043import android.net.NetworkInfo;
Jeff Sharkey82f85212012-08-24 11:17:25 -070044import android.net.RouteInfo;
Jeff Sharkey899223b2012-08-04 15:24:58 -070045import android.net.NetworkInfo.DetailedState;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070046import android.os.Binder;
Chia-chi Yehc1bac3a2011-12-16 15:00:31 -080047import android.os.FileUtils;
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -070048import android.os.IBinder;
Jeff Sharkey899223b2012-08-04 15:24:58 -070049import android.os.INetworkManagementService;
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -070050import android.os.Parcel;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070051import android.os.ParcelFileDescriptor;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070052import android.os.Process;
Jeff Sharkey899223b2012-08-04 15:24:58 -070053import android.os.RemoteException;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070054import android.os.SystemClock;
Jeff Sharkey088f29f2012-08-05 14:55:04 -070055import android.os.SystemService;
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -070056import android.os.UserHandle;
Jeff Sharkey82f85212012-08-24 11:17:25 -070057import android.security.Credentials;
58import android.security.KeyStore;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070059import android.util.Log;
Jeff Sharkey82f85212012-08-24 11:17:25 -070060import android.widget.Toast;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070061
62import com.android.internal.R;
Chia-chi Yeh2e467642011-07-04 03:23:12 -070063import com.android.internal.net.LegacyVpnInfo;
Chia-chi Yeh04ba25c2011-06-15 17:07:27 -070064import com.android.internal.net.VpnConfig;
Jeff Sharkey82f85212012-08-24 11:17:25 -070065import com.android.internal.net.VpnProfile;
Jeff Sharkey899223b2012-08-04 15:24:58 -070066import com.android.internal.util.Preconditions;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070067import com.android.server.ConnectivityService.VpnCallback;
Jeff Sharkey899223b2012-08-04 15:24:58 -070068import com.android.server.net.BaseNetworkObserver;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070069
Chia-chi Yeh97a61562011-07-14 15:05:05 -070070import java.io.File;
Chia-chi Yeh97a61562011-07-14 15:05:05 -070071import java.io.InputStream;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070072import java.io.OutputStream;
Jeff Sharkey82f85212012-08-24 11:17:25 -070073import java.net.Inet4Address;
74import java.net.InetAddress;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070075import java.nio.charset.Charsets;
Chia-chi Yeh41d16852011-07-01 02:12:06 -070076import java.util.Arrays;
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -070077import java.util.concurrent.atomic.AtomicInteger;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070078
Jeff Sharkey065b2992012-08-05 14:16:48 -070079import libcore.io.IoUtils;
80
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070081/**
82 * @hide
83 */
Jeff Sharkey899223b2012-08-04 15:24:58 -070084public class Vpn extends BaseNetworkStateTracker {
85 private static final String TAG = "Vpn";
86 private static final boolean LOGD = true;
87
88 // TODO: create separate trackers for each unique VPN to support
89 // automated reconnection
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070090
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070091 private final VpnCallback mCallback;
92
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -070093 private String mPackage = VpnConfig.LEGACY_VPN;
94 private String mInterface;
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -070095 private Connection mConnection;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070096 private LegacyVpnRunner mLegacyVpnRunner;
Jeff Sharkey899223b2012-08-04 15:24:58 -070097 private PendingIntent mStatusIntent;
Jeff Sharkey69ddab42012-08-25 00:05:46 -070098 private boolean mEnableNotif = true;
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -070099 private final IConnectivityManager mConnService;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700100
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -0700101 public Vpn(Context context, VpnCallback callback, INetworkManagementService netService,
102 IConnectivityManager connService) {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700103 // TODO: create dedicated TYPE_VPN network type
104 super(ConnectivityManager.TYPE_DUMMY);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700105 mContext = context;
106 mCallback = callback;
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -0700107 mConnService = connService;
Jeff Sharkey899223b2012-08-04 15:24:58 -0700108
109 try {
110 netService.registerObserver(mObserver);
111 } catch (RemoteException e) {
112 Log.wtf(TAG, "Problem registering observer", e);
113 }
114 }
115
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700116 public void setEnableNotifications(boolean enableNotif) {
117 mEnableNotif = enableNotif;
118 }
119
Jeff Sharkey899223b2012-08-04 15:24:58 -0700120 @Override
121 protected void startMonitoringInternal() {
122 // Ignored; events are sent through callbacks for now
123 }
124
125 @Override
126 public boolean teardown() {
127 // TODO: finish migration to unique tracker for each VPN
128 throw new UnsupportedOperationException();
129 }
130
131 @Override
132 public boolean reconnect() {
133 // TODO: finish migration to unique tracker for each VPN
134 throw new UnsupportedOperationException();
135 }
136
137 @Override
138 public String getTcpBufferSizesPropName() {
139 return PROP_TCP_BUFFER_UNKNOWN;
140 }
141
142 /**
143 * Update current state, dispaching event to listeners.
144 */
145 private void updateState(DetailedState detailedState, String reason) {
146 if (LOGD) Log.d(TAG, "setting state=" + detailedState + ", reason=" + reason);
147 mNetworkInfo.setDetailedState(detailedState, reason, null);
148 mCallback.onStateChanged(new NetworkInfo(mNetworkInfo));
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700149 }
150
151 /**
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700152 * Prepare for a VPN application. This method is designed to solve
153 * race conditions. It first compares the current prepared package
154 * with {@code oldPackage}. If they are the same, the prepared
155 * package is revoked and replaced with {@code newPackage}. If
156 * {@code oldPackage} is {@code null}, the comparison is omitted.
157 * If {@code newPackage} is the same package or {@code null}, the
158 * revocation is omitted. This method returns {@code true} if the
159 * operation is succeeded.
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700160 *
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700161 * Legacy VPN is handled specially since it is not a real package.
162 * It uses {@link VpnConfig#LEGACY_VPN} as its package name, and
163 * it can be revoked by itself.
164 *
165 * @param oldPackage The package name of the old VPN application.
166 * @param newPackage The package name of the new VPN application.
167 * @return true if the operation is succeeded.
Chia-chi Yehe9107902011-07-02 01:48:50 -0700168 */
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700169 public synchronized boolean prepare(String oldPackage, String newPackage) {
170 // Return false if the package does not match.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700171 if (oldPackage != null && !oldPackage.equals(mPackage)) {
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700172 return false;
Chia-chi Yehe9107902011-07-02 01:48:50 -0700173 }
174
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700175 // Return true if we do not need to revoke.
176 if (newPackage == null ||
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700177 (newPackage.equals(mPackage) && !newPackage.equals(VpnConfig.LEGACY_VPN))) {
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700178 return true;
179 }
180
Chia-chi Yehdadc8572012-06-08 13:05:58 -0700181 // Check if the caller is authorized.
182 enforceControlPermission();
Chia-chi Yehe9107902011-07-02 01:48:50 -0700183
Chia-chi Yehe9107902011-07-02 01:48:50 -0700184 // Reset the interface and hide the notification.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700185 if (mInterface != null) {
186 jniReset(mInterface);
Jeff Sharkey899223b2012-08-04 15:24:58 -0700187 final long token = Binder.clearCallingIdentity();
188 try {
189 mCallback.restore();
190 hideNotification();
191 } finally {
192 Binder.restoreCallingIdentity(token);
193 }
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700194 mInterface = null;
Chia-chi Yehe9107902011-07-02 01:48:50 -0700195 }
196
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700197 // Revoke the connection or stop LegacyVpnRunner.
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700198 if (mConnection != null) {
199 try {
200 mConnection.mService.transact(IBinder.LAST_CALL_TRANSACTION,
201 Parcel.obtain(), null, IBinder.FLAG_ONEWAY);
202 } catch (Exception e) {
203 // ignore
204 }
205 mContext.unbindService(mConnection);
206 mConnection = null;
Chia-chi Yehe9107902011-07-02 01:48:50 -0700207 } else if (mLegacyVpnRunner != null) {
208 mLegacyVpnRunner.exit();
209 mLegacyVpnRunner = null;
210 }
211
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700212 Log.i(TAG, "Switched from " + mPackage + " to " + newPackage);
213 mPackage = newPackage;
Jeff Sharkey899223b2012-08-04 15:24:58 -0700214 updateState(DetailedState.IDLE, "prepare");
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700215 return true;
Chia-chi Yehe9107902011-07-02 01:48:50 -0700216 }
217
218 /**
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700219 * Protect a socket from routing changes by binding it to the given
220 * interface. The socket is NOT closed by this method.
221 *
222 * @param socket The socket to be bound.
Jeff Sharkey899223b2012-08-04 15:24:58 -0700223 * @param interfaze The name of the interface.
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700224 */
225 public void protect(ParcelFileDescriptor socket, String interfaze) throws Exception {
226 PackageManager pm = mContext.getPackageManager();
227 ApplicationInfo app = pm.getApplicationInfo(mPackage, 0);
228 if (Binder.getCallingUid() != app.uid) {
229 throw new SecurityException("Unauthorized Caller");
230 }
231 jniProtect(socket.getFd(), interfaze);
232 }
233
234 /**
Chia-chi Yehe9107902011-07-02 01:48:50 -0700235 * Establish a VPN network and return the file descriptor of the VPN
236 * interface. This methods returns {@code null} if the application is
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700237 * revoked or not prepared.
Chia-chi Yehe9107902011-07-02 01:48:50 -0700238 *
239 * @param config The parameters to configure the network.
240 * @return The file descriptor of the VPN interface.
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700241 */
Chia-chi Yeh04ba25c2011-06-15 17:07:27 -0700242 public synchronized ParcelFileDescriptor establish(VpnConfig config) {
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700243 // Check if the caller is already prepared.
244 PackageManager pm = mContext.getPackageManager();
245 ApplicationInfo app = null;
246 try {
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700247 app = pm.getApplicationInfo(mPackage, 0);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700248 } catch (Exception e) {
Chia-chi Yeh7b0b8342011-06-17 14:34:11 -0700249 return null;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700250 }
251 if (Binder.getCallingUid() != app.uid) {
Chia-chi Yeh7b0b8342011-06-17 14:34:11 -0700252 return null;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700253 }
254
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700255 // Check if the service is properly declared.
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700256 Intent intent = new Intent(VpnConfig.SERVICE_INTERFACE);
257 intent.setClassName(mPackage, config.user);
258 ResolveInfo info = pm.resolveService(intent, 0);
259 if (info == null) {
260 throw new SecurityException("Cannot find " + config.user);
261 }
262 if (!BIND_VPN_SERVICE.equals(info.serviceInfo.permission)) {
263 throw new SecurityException(config.user + " does not require " + BIND_VPN_SERVICE);
264 }
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700265
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700266 // Load the label.
267 String label = app.loadLabel(pm).toString();
268
269 // Load the icon and convert it into a bitmap.
270 Drawable icon = app.loadIcon(pm);
271 Bitmap bitmap = null;
272 if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) {
273 int width = mContext.getResources().getDimensionPixelSize(
274 android.R.dimen.notification_large_icon_width);
275 int height = mContext.getResources().getDimensionPixelSize(
276 android.R.dimen.notification_large_icon_height);
277 icon.setBounds(0, 0, width, height);
278 bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Dianne Hackborn6311d0a2011-08-02 16:37:58 -0700279 Canvas c = new Canvas(bitmap);
280 icon.draw(c);
281 c.setBitmap(null);
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700282 }
283
Chia-chi Yehe9107902011-07-02 01:48:50 -0700284 // Configure the interface. Abort if any of these steps fails.
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700285 ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu));
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700286 try {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700287 updateState(DetailedState.CONNECTING, "establish");
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700288 String interfaze = jniGetName(tun.getFd());
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700289 if (jniSetAddresses(interfaze, config.addresses) < 1) {
290 throw new IllegalArgumentException("At least one address must be specified");
291 }
292 if (config.routes != null) {
293 jniSetRoutes(interfaze, config.routes);
294 }
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700295 Connection connection = new Connection();
296 if (!mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
297 throw new IllegalStateException("Cannot bind " + config.user);
298 }
299 if (mConnection != null) {
300 mContext.unbindService(mConnection);
301 }
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700302 if (mInterface != null && !mInterface.equals(interfaze)) {
303 jniReset(mInterface);
Chia-chi Yehf4e3bf82011-06-30 12:33:17 -0700304 }
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700305 mConnection = connection;
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700306 mInterface = interfaze;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700307 } catch (RuntimeException e) {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700308 updateState(DetailedState.FAILED, "establish");
Jeff Sharkey065b2992012-08-05 14:16:48 -0700309 IoUtils.closeQuietly(tun);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700310 throw e;
311 }
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700312 Log.i(TAG, "Established by " + config.user + " on " + mInterface);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700313
Chia-chi Yeh8909b102011-07-01 01:09:42 -0700314 // Fill more values.
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700315 config.user = mPackage;
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700316 config.interfaze = mInterface;
Chia-chi Yeh8909b102011-07-01 01:09:42 -0700317
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700318 // Override DNS servers and show the notification.
Jeff Sharkey899223b2012-08-04 15:24:58 -0700319 final long token = Binder.clearCallingIdentity();
320 try {
321 mCallback.override(config.dnsServers, config.searchDomains);
322 showNotification(config, label, bitmap);
323 } finally {
324 Binder.restoreCallingIdentity(token);
325 }
326 // TODO: ensure that contract class eventually marks as connected
327 updateState(DetailedState.AUTHENTICATING, "establish");
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700328 return tun;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700329 }
330
Jeff Sharkey899223b2012-08-04 15:24:58 -0700331 @Deprecated
332 public synchronized void interfaceStatusChanged(String iface, boolean up) {
333 try {
334 mObserver.interfaceStatusChanged(iface, up);
335 } catch (RemoteException e) {
336 // ignored; target is local
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700337 }
338 }
339
Jeff Sharkey899223b2012-08-04 15:24:58 -0700340 private INetworkManagementEventObserver mObserver = new BaseNetworkObserver() {
341 @Override
342 public void interfaceStatusChanged(String interfaze, boolean up) {
343 synchronized (Vpn.this) {
344 if (!up && mLegacyVpnRunner != null) {
345 mLegacyVpnRunner.check(interfaze);
346 }
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700347 }
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700348 }
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700349
Jeff Sharkey899223b2012-08-04 15:24:58 -0700350 @Override
351 public void interfaceRemoved(String interfaze) {
352 synchronized (Vpn.this) {
353 if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) {
354 final long token = Binder.clearCallingIdentity();
355 try {
356 mCallback.restore();
357 hideNotification();
358 } finally {
359 Binder.restoreCallingIdentity(token);
360 }
361 mInterface = null;
362 if (mConnection != null) {
363 mContext.unbindService(mConnection);
364 mConnection = null;
365 updateState(DetailedState.DISCONNECTED, "interfaceRemoved");
366 } else if (mLegacyVpnRunner != null) {
367 mLegacyVpnRunner.exit();
368 mLegacyVpnRunner = null;
369 }
370 }
371 }
372 }
373 };
Haoyu Baidb3c8672012-06-20 14:29:57 -0700374
Chia-chi Yehdadc8572012-06-08 13:05:58 -0700375 private void enforceControlPermission() {
376 // System user is allowed to control VPN.
377 if (Binder.getCallingUid() == Process.SYSTEM_UID) {
378 return;
379 }
380
381 try {
382 // System dialogs are also allowed to control VPN.
383 PackageManager pm = mContext.getPackageManager();
384 ApplicationInfo app = pm.getApplicationInfo(VpnConfig.DIALOGS_PACKAGE, 0);
385 if (Binder.getCallingUid() == app.uid) {
386 return;
387 }
388 } catch (Exception e) {
389 // ignore
390 }
391
392 throw new SecurityException("Unauthorized Caller");
393 }
394
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700395 private class Connection implements ServiceConnection {
396 private IBinder mService;
397
398 @Override
399 public void onServiceConnected(ComponentName name, IBinder service) {
400 mService = service;
401 }
402
403 @Override
404 public void onServiceDisconnected(ComponentName name) {
405 mService = null;
406 }
407 }
408
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700409 private void showNotification(VpnConfig config, String label, Bitmap icon) {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700410 if (!mEnableNotif) return;
Jeff Sharkey899223b2012-08-04 15:24:58 -0700411 mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext, config);
412
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700413 NotificationManager nm = (NotificationManager)
414 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
415
416 if (nm != null) {
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700417 String title = (label == null) ? mContext.getString(R.string.vpn_title) :
418 mContext.getString(R.string.vpn_title_long, label);
Chia-chi Yeh34e78132011-07-03 03:07:07 -0700419 String text = (config.session == null) ? mContext.getString(R.string.vpn_text) :
420 mContext.getString(R.string.vpn_text_long, config.session);
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700421 config.startTime = SystemClock.elapsedRealtime();
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700422
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700423 Notification notification = new Notification.Builder(mContext)
424 .setSmallIcon(R.drawable.vpn_connected)
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700425 .setLargeIcon(icon)
426 .setContentTitle(title)
Chia-chi Yehf8905fd2011-06-14 16:35:02 -0700427 .setContentText(text)
Jeff Sharkey899223b2012-08-04 15:24:58 -0700428 .setContentIntent(mStatusIntent)
Chia-chi Yeh50fe7092012-01-11 14:26:24 -0800429 .setDefaults(0)
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700430 .setOngoing(true)
Jeff Sharkey899223b2012-08-04 15:24:58 -0700431 .build();
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700432 nm.notifyAsUser(null, R.drawable.vpn_connected, notification, UserHandle.ALL);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700433 }
434 }
435
436 private void hideNotification() {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700437 if (!mEnableNotif) return;
Jeff Sharkey899223b2012-08-04 15:24:58 -0700438 mStatusIntent = null;
439
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700440 NotificationManager nm = (NotificationManager)
441 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
442
443 if (nm != null) {
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -0700444 nm.cancelAsUser(null, R.drawable.vpn_connected, UserHandle.ALL);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700445 }
446 }
447
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700448 private native int jniCreate(int mtu);
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700449 private native String jniGetName(int tun);
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700450 private native int jniSetAddresses(String interfaze, String addresses);
451 private native int jniSetRoutes(String interfaze, String routes);
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700452 private native void jniReset(String interfaze);
453 private native int jniCheck(String interfaze);
454 private native void jniProtect(int socket, String interfaze);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700455
Jeff Sharkey82f85212012-08-24 11:17:25 -0700456 private static String findLegacyVpnGateway(LinkProperties prop) {
457 for (RouteInfo route : prop.getRoutes()) {
458 // Currently legacy VPN only works on IPv4.
459 if (route.isDefaultRoute() && route.getGateway() instanceof Inet4Address) {
460 return route.getGateway().getHostAddress();
461 }
462 }
Jeff Sharkey899223b2012-08-04 15:24:58 -0700463
Jeff Sharkey82f85212012-08-24 11:17:25 -0700464 throw new IllegalStateException("Unable to find suitable gateway");
465 }
466
467 /**
468 * Start legacy VPN, controlling native daemons as needed. Creates a
469 * secondary thread to perform connection work, returning quickly.
470 */
471 public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, LinkProperties egress) {
Robert Greenwalt5a6bdc42013-02-15 10:56:35 -0800472 enforceControlPermission();
Kenny Rootb9594ce2013-02-14 10:18:38 -0800473 if (!keyStore.isUnlocked()) {
Jeff Sharkey82f85212012-08-24 11:17:25 -0700474 throw new IllegalStateException("KeyStore isn't unlocked");
475 }
476
477 final String iface = egress.getInterfaceName();
478 final String gateway = findLegacyVpnGateway(egress);
479
480 // Load certificates.
481 String privateKey = "";
482 String userCert = "";
483 String caCert = "";
484 String serverCert = "";
485 if (!profile.ipsecUserCert.isEmpty()) {
486 privateKey = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert;
487 byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert);
488 userCert = (value == null) ? null : new String(value, Charsets.UTF_8);
489 }
490 if (!profile.ipsecCaCert.isEmpty()) {
491 byte[] value = keyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert);
492 caCert = (value == null) ? null : new String(value, Charsets.UTF_8);
493 }
494 if (!profile.ipsecServerCert.isEmpty()) {
495 byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecServerCert);
496 serverCert = (value == null) ? null : new String(value, Charsets.UTF_8);
497 }
498 if (privateKey == null || userCert == null || caCert == null || serverCert == null) {
499 throw new IllegalStateException("Cannot load credentials");
500 }
501
502 // Prepare arguments for racoon.
503 String[] racoon = null;
504 switch (profile.type) {
505 case VpnProfile.TYPE_L2TP_IPSEC_PSK:
506 racoon = new String[] {
507 iface, profile.server, "udppsk", profile.ipsecIdentifier,
508 profile.ipsecSecret, "1701",
509 };
510 break;
511 case VpnProfile.TYPE_L2TP_IPSEC_RSA:
512 racoon = new String[] {
513 iface, profile.server, "udprsa", privateKey, userCert,
514 caCert, serverCert, "1701",
515 };
516 break;
517 case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
518 racoon = new String[] {
519 iface, profile.server, "xauthpsk", profile.ipsecIdentifier,
520 profile.ipsecSecret, profile.username, profile.password, "", gateway,
521 };
522 break;
523 case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
524 racoon = new String[] {
525 iface, profile.server, "xauthrsa", privateKey, userCert,
526 caCert, serverCert, profile.username, profile.password, "", gateway,
527 };
528 break;
529 case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
530 racoon = new String[] {
531 iface, profile.server, "hybridrsa",
532 caCert, serverCert, profile.username, profile.password, "", gateway,
533 };
534 break;
535 }
536
537 // Prepare arguments for mtpd.
538 String[] mtpd = null;
539 switch (profile.type) {
540 case VpnProfile.TYPE_PPTP:
541 mtpd = new String[] {
542 iface, "pptp", profile.server, "1723",
543 "name", profile.username, "password", profile.password,
544 "linkname", "vpn", "refuse-eap", "nodefaultroute",
545 "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
546 (profile.mppe ? "+mppe" : "nomppe"),
547 };
548 break;
549 case VpnProfile.TYPE_L2TP_IPSEC_PSK:
550 case VpnProfile.TYPE_L2TP_IPSEC_RSA:
551 mtpd = new String[] {
552 iface, "l2tp", profile.server, "1701", profile.l2tpSecret,
553 "name", profile.username, "password", profile.password,
554 "linkname", "vpn", "refuse-eap", "nodefaultroute",
555 "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
556 };
557 break;
558 }
559
560 VpnConfig config = new VpnConfig();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700561 config.legacy = true;
Jeff Sharkey82f85212012-08-24 11:17:25 -0700562 config.user = profile.key;
563 config.interfaze = iface;
564 config.session = profile.name;
565 config.routes = profile.routes;
566 if (!profile.dnsServers.isEmpty()) {
567 config.dnsServers = Arrays.asList(profile.dnsServers.split(" +"));
568 }
569 if (!profile.searchDomains.isEmpty()) {
570 config.searchDomains = Arrays.asList(profile.searchDomains.split(" +"));
571 }
Jeff Sharkey82f85212012-08-24 11:17:25 -0700572 startLegacyVpn(config, racoon, mtpd);
573 }
574
575 private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
576 stopLegacyVpn();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700577
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700578 // Prepare for the new request. This also checks the caller.
579 prepare(null, VpnConfig.LEGACY_VPN);
Jeff Sharkey899223b2012-08-04 15:24:58 -0700580 updateState(DetailedState.CONNECTING, "startLegacyVpn");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700581
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700582 // Start a new LegacyVpnRunner and we are done!
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700583 mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd);
584 mLegacyVpnRunner.start();
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700585 }
586
Jeff Sharkey899223b2012-08-04 15:24:58 -0700587 public synchronized void stopLegacyVpn() {
588 if (mLegacyVpnRunner != null) {
589 mLegacyVpnRunner.exit();
590 mLegacyVpnRunner = null;
591
592 synchronized (LegacyVpnRunner.TAG) {
593 // wait for old thread to completely finish before spinning up
594 // new instance, otherwise state updates can be out of order.
595 }
596 }
597 }
598
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700599 /**
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700600 * Return the information of the current ongoing legacy VPN.
601 */
602 public synchronized LegacyVpnInfo getLegacyVpnInfo() {
Chia-chi Yehdadc8572012-06-08 13:05:58 -0700603 // Check if the caller is authorized.
604 enforceControlPermission();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700605 if (mLegacyVpnRunner == null) return null;
606
607 final LegacyVpnInfo info = new LegacyVpnInfo();
608 info.key = mLegacyVpnRunner.mConfig.user;
609 info.state = LegacyVpnInfo.stateFromNetworkInfo(mNetworkInfo);
610 if (mNetworkInfo.isConnected()) {
611 info.intent = mStatusIntent;
612 }
613 return info;
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700614 }
615
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700616 public VpnConfig getLegacyVpnConfig() {
617 if (mLegacyVpnRunner != null) {
618 return mLegacyVpnRunner.mConfig;
619 } else {
620 return null;
621 }
622 }
623
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700624 /**
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700625 * Bringing up a VPN connection takes time, and that is all this thread
626 * does. Here we have plenty of time. The only thing we need to take
627 * care of is responding to interruptions as soon as possible. Otherwise
628 * requests will be piled up. This can be done in a Handler as a state
629 * machine, but it is much easier to read in the current form.
630 */
631 private class LegacyVpnRunner extends Thread {
632 private static final String TAG = "LegacyVpnRunner";
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700633
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700634 private final VpnConfig mConfig;
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700635 private final String[] mDaemons;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700636 private final String[][] mArguments;
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700637 private final LocalSocket[] mSockets;
Robert Greenwalt53c04bd2012-10-12 17:02:45 -0700638 private final String mOuterInterface;
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -0700639 private final AtomicInteger mOuterConnection =
640 new AtomicInteger(ConnectivityManager.TYPE_NONE);
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700641
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700642 private long mTimer = -1;
643
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -0700644 /**
645 * Watch for the outer connection (passing in the constructor) going away.
646 */
647 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
648 @Override
649 public void onReceive(Context context, Intent intent) {
650 if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
651 if (intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE,
652 ConnectivityManager.TYPE_NONE) == mOuterConnection.get()) {
653 NetworkInfo info = (NetworkInfo)intent.getExtra(
654 ConnectivityManager.EXTRA_NETWORK_INFO);
655 if (info != null && !info.isConnectedOrConnecting()) {
656 try {
657 mObserver.interfaceStatusChanged(mOuterInterface, false);
658 } catch (RemoteException e) {}
659 }
660 }
661 }
662 }
663 };
664
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700665 public LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700666 super(TAG);
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700667 mConfig = config;
668 mDaemons = new String[] {"racoon", "mtpd"};
Jeff Sharkey899223b2012-08-04 15:24:58 -0700669 // TODO: clear arguments from memory once launched
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700670 mArguments = new String[][] {racoon, mtpd};
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700671 mSockets = new LocalSocket[mDaemons.length];
Robert Greenwalt53c04bd2012-10-12 17:02:45 -0700672
673 // This is the interface which VPN is running on,
674 // mConfig.interfaze will change to point to OUR
675 // internal interface soon. TODO - add inner/outer to mconfig
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -0700676 // TODO - we have a race - if the outer iface goes away/disconnects before we hit this
677 // we will leave the VPN up. We should check that it's still there/connected after
678 // registering
Robert Greenwalt53c04bd2012-10-12 17:02:45 -0700679 mOuterInterface = mConfig.interfaze;
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -0700680
681 try {
682 mOuterConnection.set(
683 mConnService.findConnectionTypeForIface(mOuterInterface));
684 } catch (Exception e) {
685 mOuterConnection.set(ConnectivityManager.TYPE_NONE);
686 }
687
688 IntentFilter filter = new IntentFilter();
689 filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
690 mContext.registerReceiver(mBroadcastReceiver, filter);
691
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700692 }
693
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700694 public void check(String interfaze) {
Robert Greenwalt53c04bd2012-10-12 17:02:45 -0700695 if (interfaze.equals(mOuterInterface)) {
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700696 Log.i(TAG, "Legacy VPN is going down with " + interfaze);
697 exit();
698 }
699 }
700
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700701 public void exit() {
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700702 // We assume that everything is reset after stopping the daemons.
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700703 interrupt();
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700704 for (LocalSocket socket : mSockets) {
Jeff Sharkey065b2992012-08-05 14:16:48 -0700705 IoUtils.closeQuietly(socket);
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700706 }
Jeff Sharkey899223b2012-08-04 15:24:58 -0700707 updateState(DetailedState.DISCONNECTED, "exit");
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -0700708 try {
709 mContext.unregisterReceiver(mBroadcastReceiver);
710 } catch (IllegalArgumentException e) {}
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700711 }
712
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700713 @Override
714 public void run() {
715 // Wait for the previous thread since it has been interrupted.
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700716 Log.v(TAG, "Waiting");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700717 synchronized (TAG) {
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700718 Log.v(TAG, "Executing");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700719 execute();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700720 monitorDaemons();
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700721 }
722 }
723
724 private void checkpoint(boolean yield) throws InterruptedException {
725 long now = SystemClock.elapsedRealtime();
726 if (mTimer == -1) {
727 mTimer = now;
728 Thread.sleep(1);
Chia-chi Yeh7ef86112011-07-22 15:46:52 -0700729 } else if (now - mTimer <= 60000) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700730 Thread.sleep(yield ? 200 : 1);
731 } else {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700732 updateState(DetailedState.FAILED, "checkpoint");
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700733 throw new IllegalStateException("Time is up");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700734 }
735 }
736
737 private void execute() {
738 // Catch all exceptions so we can clean up few things.
Jeff Sharkey899223b2012-08-04 15:24:58 -0700739 boolean initFinished = false;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700740 try {
741 // Initialize the timer.
742 checkpoint(false);
743
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700744 // Wait for the daemons to stop.
745 for (String daemon : mDaemons) {
Jeff Sharkey088f29f2012-08-05 14:55:04 -0700746 while (!SystemService.isStopped(daemon)) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700747 checkpoint(true);
748 }
749 }
750
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700751 // Clear the previous state.
752 File state = new File("/data/misc/vpn/state");
753 state.delete();
754 if (state.exists()) {
755 throw new IllegalStateException("Cannot delete the state");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700756 }
Chia-chi Yehc1872732011-12-08 16:51:41 -0800757 new File("/data/misc/vpn/abort").delete();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700758 initFinished = true;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700759
Chia-chi Yehe9107902011-07-02 01:48:50 -0700760 // Check if we need to restart any of the daemons.
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700761 boolean restart = false;
762 for (String[] arguments : mArguments) {
763 restart = restart || (arguments != null);
764 }
765 if (!restart) {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700766 updateState(DetailedState.DISCONNECTED, "execute");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700767 return;
768 }
Jeff Sharkey899223b2012-08-04 15:24:58 -0700769 updateState(DetailedState.CONNECTING, "execute");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700770
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700771 // Start the daemon with arguments.
772 for (int i = 0; i < mDaemons.length; ++i) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700773 String[] arguments = mArguments[i];
774 if (arguments == null) {
775 continue;
776 }
777
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700778 // Start the daemon.
779 String daemon = mDaemons[i];
Jeff Sharkey088f29f2012-08-05 14:55:04 -0700780 SystemService.start(daemon);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700781
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700782 // Wait for the daemon to start.
Jeff Sharkey088f29f2012-08-05 14:55:04 -0700783 while (!SystemService.isRunning(daemon)) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700784 checkpoint(true);
785 }
786
787 // Create the control socket.
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700788 mSockets[i] = new LocalSocket();
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700789 LocalSocketAddress address = new LocalSocketAddress(
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700790 daemon, LocalSocketAddress.Namespace.RESERVED);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700791
792 // Wait for the socket to connect.
793 while (true) {
794 try {
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700795 mSockets[i].connect(address);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700796 break;
797 } catch (Exception e) {
798 // ignore
799 }
800 checkpoint(true);
801 }
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700802 mSockets[i].setSoTimeout(500);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700803
804 // Send over the arguments.
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700805 OutputStream out = mSockets[i].getOutputStream();
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700806 for (String argument : arguments) {
807 byte[] bytes = argument.getBytes(Charsets.UTF_8);
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700808 if (bytes.length >= 0xFFFF) {
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700809 throw new IllegalArgumentException("Argument is too large");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700810 }
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700811 out.write(bytes.length >> 8);
812 out.write(bytes.length);
813 out.write(bytes);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700814 checkpoint(false);
815 }
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700816 out.write(0xFF);
817 out.write(0xFF);
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700818 out.flush();
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700819
820 // Wait for End-of-File.
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700821 InputStream in = mSockets[i].getInputStream();
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700822 while (true) {
823 try {
824 if (in.read() == -1) {
825 break;
826 }
827 } catch (Exception e) {
828 // ignore
829 }
830 checkpoint(true);
831 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700832 }
833
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700834 // Wait for the daemons to create the new state.
835 while (!state.exists()) {
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700836 // Check if a running daemon is dead.
837 for (int i = 0; i < mDaemons.length; ++i) {
838 String daemon = mDaemons[i];
Jeff Sharkey088f29f2012-08-05 14:55:04 -0700839 if (mArguments[i] != null && !SystemService.isRunning(daemon)) {
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700840 throw new IllegalStateException(daemon + " is dead");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700841 }
842 }
843 checkpoint(true);
844 }
845
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700846 // Now we are connected. Read and parse the new state.
Chia-chi Yehc1bac3a2011-12-16 15:00:31 -0800847 String[] parameters = FileUtils.readTextFile(state, 0, null).split("\n", -1);
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700848 if (parameters.length != 6) {
849 throw new IllegalStateException("Cannot parse the state");
850 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700851
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700852 // Set the interface and the addresses in the config.
853 mConfig.interfaze = parameters[0].trim();
854 mConfig.addresses = parameters[1].trim();
855
856 // Set the routes if they are not set in the config.
857 if (mConfig.routes == null || mConfig.routes.isEmpty()) {
858 mConfig.routes = parameters[2].trim();
859 }
860
861 // Set the DNS servers if they are not set in the config.
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700862 if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700863 String dnsServers = parameters[3].trim();
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700864 if (!dnsServers.isEmpty()) {
865 mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
866 }
867 }
868
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700869 // Set the search domains if they are not set in the config.
870 if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) {
871 String searchDomains = parameters[4].trim();
872 if (!searchDomains.isEmpty()) {
873 mConfig.searchDomains = Arrays.asList(searchDomains.split(" "));
874 }
875 }
Chia-chi Yehe9107902011-07-02 01:48:50 -0700876
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700877 // Set the routes.
878 jniSetRoutes(mConfig.interfaze, mConfig.routes);
879
880 // Here is the last step and it must be done synchronously.
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700881 synchronized (Vpn.this) {
882 // Check if the thread is interrupted while we are waiting.
883 checkpoint(false);
884
Chia-chi Yehe9107902011-07-02 01:48:50 -0700885 // Check if the interface is gone while we are waiting.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700886 if (jniCheck(mConfig.interfaze) == 0) {
Chia-chi Yeh34e78132011-07-03 03:07:07 -0700887 throw new IllegalStateException(mConfig.interfaze + " is gone");
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700888 }
Chia-chi Yehe9107902011-07-02 01:48:50 -0700889
890 // Now INetworkManagementEventObserver is watching our back.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700891 mInterface = mConfig.interfaze;
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700892 mCallback.override(mConfig.dnsServers, mConfig.searchDomains);
893 showNotification(mConfig, null, null);
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700894
895 Log.i(TAG, "Connected!");
Jeff Sharkey899223b2012-08-04 15:24:58 -0700896 updateState(DetailedState.CONNECTED, "execute");
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700897 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700898 } catch (Exception e) {
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700899 Log.i(TAG, "Aborting", e);
Chia-chi Yehe9107902011-07-02 01:48:50 -0700900 exit();
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700901 } finally {
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700902 // Kill the daemons if they fail to stop.
Jeff Sharkey899223b2012-08-04 15:24:58 -0700903 if (!initFinished) {
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700904 for (String daemon : mDaemons) {
Jeff Sharkey088f29f2012-08-05 14:55:04 -0700905 SystemService.stop(daemon);
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700906 }
907 }
908
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700909 // Do not leave an unstable state.
Jeff Sharkey899223b2012-08-04 15:24:58 -0700910 if (!initFinished || mNetworkInfo.getDetailedState() == DetailedState.CONNECTING) {
911 updateState(DetailedState.FAILED, "execute");
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700912 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700913 }
914 }
Jeff Sharkey899223b2012-08-04 15:24:58 -0700915
916 /**
917 * Monitor the daemons we started, moving to disconnected state if the
918 * underlying services fail.
919 */
920 private void monitorDaemons() {
921 if (!mNetworkInfo.isConnected()) {
922 return;
923 }
924
925 try {
926 while (true) {
927 Thread.sleep(2000);
928 for (int i = 0; i < mDaemons.length; i++) {
929 if (mArguments[i] != null && SystemService.isStopped(mDaemons[i])) {
930 return;
931 }
932 }
933 }
934 } catch (InterruptedException e) {
935 Log.d(TAG, "interrupted during monitorDaemons(); stopping services");
936 } finally {
937 for (String daemon : mDaemons) {
938 SystemService.stop(daemon);
939 }
940
941 updateState(DetailedState.DISCONNECTED, "babysit");
942 }
943 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700944 }
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700945}