blob: d87387fb2874cdf6d1af57fef86271392d53787b [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
Chad Brubaker4ca19e82013-06-14 11:16:51 -070021import android.app.AppGlobals;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070022import android.app.Notification;
23import android.app.NotificationManager;
Jeff Sharkey899223b2012-08-04 15:24:58 -070024import android.app.PendingIntent;
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -070025import android.content.BroadcastReceiver;
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -070026import android.content.ComponentName;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070027import android.content.Context;
28import android.content.Intent;
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -070029import android.content.IntentFilter;
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -070030import android.content.ServiceConnection;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070031import android.content.pm.ApplicationInfo;
32import android.content.pm.PackageManager;
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -070033import android.content.pm.ResolveInfo;
Chad Brubakerc2865192013-07-10 14:46:23 -070034import android.content.pm.UserInfo;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070035import android.graphics.Bitmap;
36import android.graphics.Canvas;
37import android.graphics.drawable.Drawable;
Jeff Sharkey899223b2012-08-04 15:24:58 -070038import android.net.BaseNetworkStateTracker;
39import android.net.ConnectivityManager;
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -070040import android.net.IConnectivityManager;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070041import android.net.INetworkManagementEventObserver;
Chad Brubaker4ca19e82013-06-14 11:16:51 -070042import android.net.LinkAddress;
Jeff Sharkey82f85212012-08-24 11:17:25 -070043import android.net.LinkProperties;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070044import android.net.LocalSocket;
45import android.net.LocalSocketAddress;
Jeff Sharkey899223b2012-08-04 15:24:58 -070046import android.net.NetworkInfo;
Jeff Sharkey82f85212012-08-24 11:17:25 -070047import android.net.RouteInfo;
Jeff Sharkey899223b2012-08-04 15:24:58 -070048import android.net.NetworkInfo.DetailedState;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070049import android.os.Binder;
Chia-chi Yehc1bac3a2011-12-16 15:00:31 -080050import android.os.FileUtils;
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -070051import android.os.IBinder;
Jeff Sharkey899223b2012-08-04 15:24:58 -070052import android.os.INetworkManagementService;
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -070053import android.os.Parcel;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070054import android.os.ParcelFileDescriptor;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070055import android.os.Process;
Jeff Sharkey899223b2012-08-04 15:24:58 -070056import android.os.RemoteException;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070057import android.os.SystemClock;
Jeff Sharkey088f29f2012-08-05 14:55:04 -070058import android.os.SystemService;
Dianne Hackborn50cdf7c32012-09-23 17:08:57 -070059import android.os.UserHandle;
Chad Brubakerc2865192013-07-10 14:46:23 -070060import android.os.UserManager;
Jeff Sharkey82f85212012-08-24 11:17:25 -070061import android.security.Credentials;
62import android.security.KeyStore;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070063import android.util.Log;
Chad Brubakerc2865192013-07-10 14:46:23 -070064import android.util.SparseBooleanArray;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070065
Chad Brubakerc2865192013-07-10 14:46:23 -070066import com.android.internal.annotations.GuardedBy;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070067import com.android.internal.R;
Chia-chi Yeh2e467642011-07-04 03:23:12 -070068import com.android.internal.net.LegacyVpnInfo;
Chia-chi Yeh04ba25c2011-06-15 17:07:27 -070069import com.android.internal.net.VpnConfig;
Jeff Sharkey82f85212012-08-24 11:17:25 -070070import com.android.internal.net.VpnProfile;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070071import com.android.server.ConnectivityService.VpnCallback;
Jeff Sharkey899223b2012-08-04 15:24:58 -070072import com.android.server.net.BaseNetworkObserver;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070073
Chia-chi Yeh97a61562011-07-14 15:05:05 -070074import java.io.File;
Chia-chi Yeh97a61562011-07-14 15:05:05 -070075import java.io.InputStream;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070076import java.io.OutputStream;
Jeff Sharkey82f85212012-08-24 11:17:25 -070077import java.net.Inet4Address;
Elliott Hughesd396a442013-06-28 16:24:48 -070078import java.nio.charset.StandardCharsets;
Chia-chi Yeh41d16852011-07-01 02:12:06 -070079import java.util.Arrays;
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -070080import java.util.concurrent.atomic.AtomicInteger;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070081
Jeff Sharkey065b2992012-08-05 14:16:48 -070082import libcore.io.IoUtils;
83
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070084/**
85 * @hide
86 */
Jeff Sharkey899223b2012-08-04 15:24:58 -070087public class Vpn extends BaseNetworkStateTracker {
88 private static final String TAG = "Vpn";
89 private static final boolean LOGD = true;
90
91 // TODO: create separate trackers for each unique VPN to support
92 // automated reconnection
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070093
Chia-chi Yehff3bdca2011-05-23 17:26:46 -070094 private final VpnCallback mCallback;
95
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -070096 private String mPackage = VpnConfig.LEGACY_VPN;
97 private String mInterface;
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -070098 private Connection mConnection;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -070099 private LegacyVpnRunner mLegacyVpnRunner;
Jeff Sharkey899223b2012-08-04 15:24:58 -0700100 private PendingIntent mStatusIntent;
Jeff Sharkey57666932013-04-30 17:01:57 -0700101 private volatile boolean mEnableNotif = true;
102 private volatile boolean mEnableTeardown = true;
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -0700103 private final IConnectivityManager mConnService;
Chad Brubakerc2865192013-07-10 14:46:23 -0700104 private VpnConfig mConfig;
105
106 /* list of users using this VPN. */
107 @GuardedBy("this")
108 private SparseBooleanArray mVpnUsers = null;
109 private BroadcastReceiver mUserIntentReceiver = null;
110
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700111 private final int mUserId;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700112
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -0700113 public Vpn(Context context, VpnCallback callback, INetworkManagementService netService,
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700114 IConnectivityManager connService, int userId) {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700115 // TODO: create dedicated TYPE_VPN network type
116 super(ConnectivityManager.TYPE_DUMMY);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700117 mContext = context;
118 mCallback = callback;
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -0700119 mConnService = connService;
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700120 mUserId = userId;
Jeff Sharkey899223b2012-08-04 15:24:58 -0700121
122 try {
123 netService.registerObserver(mObserver);
124 } catch (RemoteException e) {
125 Log.wtf(TAG, "Problem registering observer", e);
126 }
Chad Brubakerc2865192013-07-10 14:46:23 -0700127 if (userId == UserHandle.USER_OWNER) {
128 // Owner's VPN also needs to handle restricted users
129 mUserIntentReceiver = new BroadcastReceiver() {
130 @Override
131 public void onReceive(Context context, Intent intent) {
132 final String action = intent.getAction();
133 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
134 UserHandle.USER_NULL);
135 if (userId == UserHandle.USER_NULL) return;
136
137 if (Intent.ACTION_USER_ADDED.equals(action)) {
138 onUserAdded(userId);
139 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
140 onUserRemoved(userId);
141 }
142 }
143 };
144
145 IntentFilter intentFilter = new IntentFilter();
146 intentFilter.addAction(Intent.ACTION_USER_ADDED);
147 intentFilter.addAction(Intent.ACTION_USER_REMOVED);
148 mContext.registerReceiverAsUser(
149 mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
150 }
Jeff Sharkey899223b2012-08-04 15:24:58 -0700151 }
152
Jeff Sharkey57666932013-04-30 17:01:57 -0700153 /**
154 * Set if this object is responsible for showing its own notifications. When
155 * {@code false}, notifications are handled externally by someone else.
156 */
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700157 public void setEnableNotifications(boolean enableNotif) {
158 mEnableNotif = enableNotif;
159 }
160
Jeff Sharkey57666932013-04-30 17:01:57 -0700161 /**
162 * Set if this object is responsible for watching for {@link NetworkInfo}
163 * teardown. When {@code false}, teardown is handled externally by someone
164 * else.
165 */
166 public void setEnableTeardown(boolean enableTeardown) {
167 mEnableTeardown = enableTeardown;
168 }
169
Jeff Sharkey899223b2012-08-04 15:24:58 -0700170 @Override
171 protected void startMonitoringInternal() {
172 // Ignored; events are sent through callbacks for now
173 }
174
175 @Override
176 public boolean teardown() {
177 // TODO: finish migration to unique tracker for each VPN
178 throw new UnsupportedOperationException();
179 }
180
181 @Override
182 public boolean reconnect() {
183 // TODO: finish migration to unique tracker for each VPN
184 throw new UnsupportedOperationException();
185 }
186
187 @Override
188 public String getTcpBufferSizesPropName() {
189 return PROP_TCP_BUFFER_UNKNOWN;
190 }
191
192 /**
193 * Update current state, dispaching event to listeners.
194 */
195 private void updateState(DetailedState detailedState, String reason) {
196 if (LOGD) Log.d(TAG, "setting state=" + detailedState + ", reason=" + reason);
197 mNetworkInfo.setDetailedState(detailedState, reason, null);
198 mCallback.onStateChanged(new NetworkInfo(mNetworkInfo));
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700199 }
200
201 /**
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700202 * Prepare for a VPN application. This method is designed to solve
203 * race conditions. It first compares the current prepared package
204 * with {@code oldPackage}. If they are the same, the prepared
205 * package is revoked and replaced with {@code newPackage}. If
206 * {@code oldPackage} is {@code null}, the comparison is omitted.
207 * If {@code newPackage} is the same package or {@code null}, the
208 * revocation is omitted. This method returns {@code true} if the
209 * operation is succeeded.
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700210 *
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700211 * Legacy VPN is handled specially since it is not a real package.
212 * It uses {@link VpnConfig#LEGACY_VPN} as its package name, and
213 * it can be revoked by itself.
214 *
215 * @param oldPackage The package name of the old VPN application.
216 * @param newPackage The package name of the new VPN application.
217 * @return true if the operation is succeeded.
Chia-chi Yehe9107902011-07-02 01:48:50 -0700218 */
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700219 public synchronized boolean prepare(String oldPackage, String newPackage) {
220 // Return false if the package does not match.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700221 if (oldPackage != null && !oldPackage.equals(mPackage)) {
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700222 return false;
Chia-chi Yehe9107902011-07-02 01:48:50 -0700223 }
224
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700225 // Return true if we do not need to revoke.
226 if (newPackage == null ||
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700227 (newPackage.equals(mPackage) && !newPackage.equals(VpnConfig.LEGACY_VPN))) {
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700228 return true;
229 }
230
Chia-chi Yehdadc8572012-06-08 13:05:58 -0700231 // Check if the caller is authorized.
232 enforceControlPermission();
Chia-chi Yehe9107902011-07-02 01:48:50 -0700233
Chia-chi Yehe9107902011-07-02 01:48:50 -0700234 // Reset the interface and hide the notification.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700235 if (mInterface != null) {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700236 final long token = Binder.clearCallingIdentity();
237 try {
238 mCallback.restore();
Chad Brubakerc2865192013-07-10 14:46:23 -0700239 final int size = mVpnUsers.size();
Robert Greenwalt69887e82013-09-24 11:05:57 -0700240 final boolean forwardDns = (mConfig.dnsServers != null &&
241 mConfig.dnsServers.size() != 0);
Chad Brubakerc2865192013-07-10 14:46:23 -0700242 for (int i = 0; i < size; i++) {
243 int user = mVpnUsers.keyAt(i);
Robert Greenwalt69887e82013-09-24 11:05:57 -0700244 mCallback.clearUserForwarding(mInterface, user, forwardDns);
Chad Brubakerc2865192013-07-10 14:46:23 -0700245 hideNotification(user);
246 }
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700247
248 mCallback.clearMarkedForwarding(mInterface);
Jeff Sharkey899223b2012-08-04 15:24:58 -0700249 } finally {
250 Binder.restoreCallingIdentity(token);
251 }
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700252 jniReset(mInterface);
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700253 mInterface = null;
Chad Brubakerc2865192013-07-10 14:46:23 -0700254 mVpnUsers = null;
Chia-chi Yehe9107902011-07-02 01:48:50 -0700255 }
256
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700257 // Revoke the connection or stop LegacyVpnRunner.
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700258 if (mConnection != null) {
259 try {
260 mConnection.mService.transact(IBinder.LAST_CALL_TRANSACTION,
261 Parcel.obtain(), null, IBinder.FLAG_ONEWAY);
262 } catch (Exception e) {
263 // ignore
264 }
265 mContext.unbindService(mConnection);
266 mConnection = null;
Chia-chi Yehe9107902011-07-02 01:48:50 -0700267 } else if (mLegacyVpnRunner != null) {
268 mLegacyVpnRunner.exit();
269 mLegacyVpnRunner = null;
270 }
271
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700272 Log.i(TAG, "Switched from " + mPackage + " to " + newPackage);
273 mPackage = newPackage;
Chad Brubakerc2865192013-07-10 14:46:23 -0700274 mConfig = null;
Jeff Sharkey899223b2012-08-04 15:24:58 -0700275 updateState(DetailedState.IDLE, "prepare");
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700276 return true;
Chia-chi Yehe9107902011-07-02 01:48:50 -0700277 }
278
279 /**
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700280 * Protect a socket from routing changes by binding it to the given
281 * interface. The socket is NOT closed by this method.
282 *
283 * @param socket The socket to be bound.
Jeff Sharkey899223b2012-08-04 15:24:58 -0700284 * @param interfaze The name of the interface.
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700285 */
286 public void protect(ParcelFileDescriptor socket, String interfaze) throws Exception {
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700287
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700288 PackageManager pm = mContext.getPackageManager();
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700289 int appUid = pm.getPackageUid(mPackage, mUserId);
290 if (Binder.getCallingUid() != appUid) {
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700291 throw new SecurityException("Unauthorized Caller");
292 }
Chad Brubakerc2865192013-07-10 14:46:23 -0700293 // protect the socket from routing rules
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700294 final long token = Binder.clearCallingIdentity();
295 try {
296 mCallback.protect(socket);
297 } finally {
298 Binder.restoreCallingIdentity(token);
299 }
Chad Brubakerc2865192013-07-10 14:46:23 -0700300 // bind the socket to the interface
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700301 jniProtect(socket.getFd(), interfaze);
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700302
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700303 }
304
305 /**
Chia-chi Yehe9107902011-07-02 01:48:50 -0700306 * Establish a VPN network and return the file descriptor of the VPN
307 * interface. This methods returns {@code null} if the application is
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700308 * revoked or not prepared.
Chia-chi Yehe9107902011-07-02 01:48:50 -0700309 *
310 * @param config The parameters to configure the network.
311 * @return The file descriptor of the VPN interface.
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700312 */
Chia-chi Yeh04ba25c2011-06-15 17:07:27 -0700313 public synchronized ParcelFileDescriptor establish(VpnConfig config) {
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700314 // Check if the caller is already prepared.
Chad Brubakerc2865192013-07-10 14:46:23 -0700315 UserManager mgr = UserManager.get(mContext);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700316 PackageManager pm = mContext.getPackageManager();
317 ApplicationInfo app = null;
318 try {
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700319 app = AppGlobals.getPackageManager().getApplicationInfo(mPackage, 0, mUserId);
320 if (Binder.getCallingUid() != app.uid) {
321 return null;
322 }
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700323 } catch (Exception e) {
Chia-chi Yeh7b0b8342011-06-17 14:34:11 -0700324 return null;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700325 }
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700326 // Check if the service is properly declared.
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700327 Intent intent = new Intent(VpnConfig.SERVICE_INTERFACE);
328 intent.setClassName(mPackage, config.user);
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700329 long token = Binder.clearCallingIdentity();
330 try {
Chad Brubakerc2865192013-07-10 14:46:23 -0700331 // Restricted users are not allowed to create VPNs, they are tied to Owner
332 UserInfo user = mgr.getUserInfo(mUserId);
333 if (user.isRestricted()) {
334 throw new SecurityException("Restricted users cannot establish VPNs");
335 }
336
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700337 ResolveInfo info = AppGlobals.getPackageManager().resolveService(intent,
338 null, 0, mUserId);
339 if (info == null) {
340 throw new SecurityException("Cannot find " + config.user);
341 }
342 if (!BIND_VPN_SERVICE.equals(info.serviceInfo.permission)) {
343 throw new SecurityException(config.user + " does not require " + BIND_VPN_SERVICE);
344 }
345 } catch (RemoteException e) {
346 throw new SecurityException("Cannot find " + config.user);
347 } finally {
348 Binder.restoreCallingIdentity(token);
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700349 }
Chia-chi Yehfcc1b412011-08-03 15:39:59 -0700350
Chia-chi Yehe9107902011-07-02 01:48:50 -0700351 // Configure the interface. Abort if any of these steps fails.
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700352 ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu));
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700353 try {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700354 updateState(DetailedState.CONNECTING, "establish");
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700355 String interfaze = jniGetName(tun.getFd());
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700356
Chad Brubakerc2865192013-07-10 14:46:23 -0700357 // TEMP use the old jni calls until there is support for netd address setting
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700358 StringBuilder builder = new StringBuilder();
359 for (LinkAddress address : config.addresses) {
360 builder.append(" " + address);
361 }
362 if (jniSetAddresses(interfaze, builder.toString()) < 1) {
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700363 throw new IllegalArgumentException("At least one address must be specified");
364 }
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700365 Connection connection = new Connection();
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700366 if (!mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
367 new UserHandle(mUserId))) {
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700368 throw new IllegalStateException("Cannot bind " + config.user);
369 }
370 if (mConnection != null) {
371 mContext.unbindService(mConnection);
372 }
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700373 if (mInterface != null && !mInterface.equals(interfaze)) {
374 jniReset(mInterface);
Chia-chi Yehf4e3bf82011-06-30 12:33:17 -0700375 }
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700376 mConnection = connection;
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700377 mInterface = interfaze;
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700378
379 // Fill more values.
380 config.user = mPackage;
381 config.interfaze = mInterface;
Chad Brubakerc2865192013-07-10 14:46:23 -0700382 config.startTime = SystemClock.elapsedRealtime();
383 mConfig = config;
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700384 // Set up forwarding and DNS rules.
Chad Brubakerc2865192013-07-10 14:46:23 -0700385 mVpnUsers = new SparseBooleanArray();
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700386 token = Binder.clearCallingIdentity();
387 try {
388 mCallback.setMarkedForwarding(mInterface);
389 mCallback.setRoutes(interfaze, config.routes);
390 mCallback.override(mInterface, config.dnsServers, config.searchDomains);
Chad Brubakerc2865192013-07-10 14:46:23 -0700391 addVpnUserLocked(mUserId);
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700392
393 } finally {
394 Binder.restoreCallingIdentity(token);
395 }
396
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700397 } catch (RuntimeException e) {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700398 updateState(DetailedState.FAILED, "establish");
Jeff Sharkey065b2992012-08-05 14:16:48 -0700399 IoUtils.closeQuietly(tun);
Chad Brubakerc2865192013-07-10 14:46:23 -0700400 // make sure marked forwarding is cleared if it was set
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700401 try {
402 mCallback.clearMarkedForwarding(mInterface);
403 } catch (Exception ingored) {
Chad Brubakerc2865192013-07-10 14:46:23 -0700404 // ignored
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700405 }
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700406 throw e;
407 }
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700408 Log.i(TAG, "Established by " + config.user + " on " + mInterface);
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700409
Chia-chi Yeh8909b102011-07-01 01:09:42 -0700410
Chad Brubakerc2865192013-07-10 14:46:23 -0700411 // If we are owner assign all Restricted Users to this VPN
412 if (mUserId == UserHandle.USER_OWNER) {
413 token = Binder.clearCallingIdentity();
414 try {
415 for (UserInfo user : mgr.getUsers()) {
416 if (user.isRestricted()) {
417 try {
418 addVpnUserLocked(user.id);
419 } catch (Exception e) {
420 Log.wtf(TAG, "Failed to add user " + user.id + " to owner's VPN");
421 }
422 }
423 }
424 } finally {
425 Binder.restoreCallingIdentity(token);
426 }
427 }
Jeff Sharkey899223b2012-08-04 15:24:58 -0700428 // TODO: ensure that contract class eventually marks as connected
429 updateState(DetailedState.AUTHENTICATING, "establish");
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700430 return tun;
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700431 }
432
Chad Brubakerc2865192013-07-10 14:46:23 -0700433 private boolean isRunningLocked() {
434 return mVpnUsers != null;
435 }
436
437 private void addVpnUserLocked(int user) {
438 enforceControlPermission();
439
440 if (!isRunningLocked()) {
441 throw new IllegalStateException("VPN is not active");
442 }
Robert Greenwalt69887e82013-09-24 11:05:57 -0700443
444 final boolean forwardDns = (mConfig.dnsServers != null &&
445 mConfig.dnsServers.size() != 0);
446
Chad Brubakerc2865192013-07-10 14:46:23 -0700447 // add the user
Robert Greenwalt69887e82013-09-24 11:05:57 -0700448 mCallback.addUserForwarding(mInterface, user, forwardDns);
Chad Brubakerc2865192013-07-10 14:46:23 -0700449 mVpnUsers.put(user, true);
450
451 // show the notification
452 if (!mPackage.equals(VpnConfig.LEGACY_VPN)) {
453 // Load everything for the user's notification
454 PackageManager pm = mContext.getPackageManager();
455 ApplicationInfo app = null;
456 try {
457 app = AppGlobals.getPackageManager().getApplicationInfo(mPackage, 0, mUserId);
458 } catch (RemoteException e) {
459 throw new IllegalStateException("Invalid application");
460 }
461 String label = app.loadLabel(pm).toString();
462 // Load the icon and convert it into a bitmap.
463 Drawable icon = app.loadIcon(pm);
464 Bitmap bitmap = null;
465 if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) {
466 int width = mContext.getResources().getDimensionPixelSize(
467 android.R.dimen.notification_large_icon_width);
468 int height = mContext.getResources().getDimensionPixelSize(
469 android.R.dimen.notification_large_icon_height);
470 icon.setBounds(0, 0, width, height);
471 bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
472 Canvas c = new Canvas(bitmap);
473 icon.draw(c);
474 c.setBitmap(null);
475 }
476 showNotification(label, bitmap, user);
477 } else {
478 showNotification(null, null, user);
479 }
480 }
481
482 private void removeVpnUserLocked(int user) {
483 enforceControlPermission();
484
485 if (!isRunningLocked()) {
486 throw new IllegalStateException("VPN is not active");
487 }
Robert Greenwalt69887e82013-09-24 11:05:57 -0700488 final boolean forwardDns = (mConfig.dnsServers != null &&
489 mConfig.dnsServers.size() != 0);
490 mCallback.clearUserForwarding(mInterface, user, forwardDns);
Chad Brubakerc2865192013-07-10 14:46:23 -0700491 mVpnUsers.delete(user);
492 hideNotification(user);
493 }
494
495 private void onUserAdded(int userId) {
496 // If the user is restricted tie them to the owner's VPN
497 synchronized(Vpn.this) {
498 UserManager mgr = UserManager.get(mContext);
499 UserInfo user = mgr.getUserInfo(userId);
500 if (user.isRestricted()) {
501 try {
502 addVpnUserLocked(userId);
503 } catch (Exception e) {
504 Log.wtf(TAG, "Failed to add restricted user to owner", e);
505 }
506 }
507 }
508 }
509
510 private void onUserRemoved(int userId) {
511 // clean up if restricted
512 synchronized(Vpn.this) {
513 UserManager mgr = UserManager.get(mContext);
514 UserInfo user = mgr.getUserInfo(userId);
515 if (user.isRestricted()) {
516 try {
517 removeVpnUserLocked(userId);
518 } catch (Exception e) {
519 Log.wtf(TAG, "Failed to remove restricted user to owner", e);
520 }
521 }
522 }
523 }
524
Chad Brubakerbf6ff2c2013-07-16 18:59:12 -0700525 /**
526 * Return the configuration of the currently running VPN.
527 */
528 public VpnConfig getVpnConfig() {
529 enforceControlPermission();
530 return mConfig;
531 }
532
Jeff Sharkey899223b2012-08-04 15:24:58 -0700533 @Deprecated
534 public synchronized void interfaceStatusChanged(String iface, boolean up) {
535 try {
536 mObserver.interfaceStatusChanged(iface, up);
537 } catch (RemoteException e) {
538 // ignored; target is local
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700539 }
540 }
541
Jeff Sharkey899223b2012-08-04 15:24:58 -0700542 private INetworkManagementEventObserver mObserver = new BaseNetworkObserver() {
543 @Override
544 public void interfaceStatusChanged(String interfaze, boolean up) {
545 synchronized (Vpn.this) {
546 if (!up && mLegacyVpnRunner != null) {
547 mLegacyVpnRunner.check(interfaze);
548 }
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700549 }
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700550 }
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700551
Jeff Sharkey899223b2012-08-04 15:24:58 -0700552 @Override
553 public void interfaceRemoved(String interfaze) {
554 synchronized (Vpn.this) {
555 if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) {
556 final long token = Binder.clearCallingIdentity();
557 try {
Chad Brubakerc2865192013-07-10 14:46:23 -0700558 final int size = mVpnUsers.size();
Robert Greenwalt69887e82013-09-24 11:05:57 -0700559 final boolean forwardDns = (mConfig.dnsServers != null &&
560 mConfig.dnsServers.size() != 0);
Chad Brubakerc2865192013-07-10 14:46:23 -0700561 for (int i = 0; i < size; i++) {
562 int user = mVpnUsers.keyAt(i);
Robert Greenwalt69887e82013-09-24 11:05:57 -0700563 mCallback.clearUserForwarding(mInterface, user, forwardDns);
Chad Brubakerc2865192013-07-10 14:46:23 -0700564 hideNotification(user);
565 }
566 mVpnUsers = null;
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700567 mCallback.clearMarkedForwarding(mInterface);
568
Jeff Sharkey899223b2012-08-04 15:24:58 -0700569 mCallback.restore();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700570 } finally {
571 Binder.restoreCallingIdentity(token);
572 }
573 mInterface = null;
574 if (mConnection != null) {
575 mContext.unbindService(mConnection);
576 mConnection = null;
577 updateState(DetailedState.DISCONNECTED, "interfaceRemoved");
578 } else if (mLegacyVpnRunner != null) {
579 mLegacyVpnRunner.exit();
580 mLegacyVpnRunner = null;
581 }
582 }
583 }
584 }
585 };
Haoyu Baidb3c8672012-06-20 14:29:57 -0700586
Chia-chi Yehdadc8572012-06-08 13:05:58 -0700587 private void enforceControlPermission() {
588 // System user is allowed to control VPN.
589 if (Binder.getCallingUid() == Process.SYSTEM_UID) {
590 return;
591 }
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700592 int appId = UserHandle.getAppId(Binder.getCallingUid());
593 final long token = Binder.clearCallingIdentity();
Chia-chi Yehdadc8572012-06-08 13:05:58 -0700594 try {
Nick Kralevich212a1952013-10-18 17:48:39 -0700595 // System VPN dialogs are also allowed to control VPN.
Chia-chi Yehdadc8572012-06-08 13:05:58 -0700596 PackageManager pm = mContext.getPackageManager();
597 ApplicationInfo app = pm.getApplicationInfo(VpnConfig.DIALOGS_PACKAGE, 0);
Nick Kralevich212a1952013-10-18 17:48:39 -0700598 if (((app.flags & ApplicationInfo.FLAG_SYSTEM) != 0) && (appId == app.uid)) {
Chia-chi Yehdadc8572012-06-08 13:05:58 -0700599 return;
600 }
601 } catch (Exception e) {
602 // ignore
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700603 } finally {
604 Binder.restoreCallingIdentity(token);
Chia-chi Yehdadc8572012-06-08 13:05:58 -0700605 }
606
607 throw new SecurityException("Unauthorized Caller");
608 }
609
Chia-chi Yeh199ed6e2011-08-03 17:38:49 -0700610 private class Connection implements ServiceConnection {
611 private IBinder mService;
612
613 @Override
614 public void onServiceConnected(ComponentName name, IBinder service) {
615 mService = service;
616 }
617
618 @Override
619 public void onServiceDisconnected(ComponentName name) {
620 mService = null;
621 }
622 }
623
Chad Brubakerc2865192013-07-10 14:46:23 -0700624 private void showNotification(String label, Bitmap icon, int user) {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700625 if (!mEnableNotif) return;
Chad Brubakerbf6ff2c2013-07-16 18:59:12 -0700626 mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext);
Jeff Sharkey899223b2012-08-04 15:24:58 -0700627
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700628 NotificationManager nm = (NotificationManager)
629 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
630
631 if (nm != null) {
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700632 String title = (label == null) ? mContext.getString(R.string.vpn_title) :
633 mContext.getString(R.string.vpn_title_long, label);
Chad Brubakerc2865192013-07-10 14:46:23 -0700634 String text = (mConfig.session == null) ? mContext.getString(R.string.vpn_text) :
635 mContext.getString(R.string.vpn_text_long, mConfig.session);
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700636
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700637 Notification notification = new Notification.Builder(mContext)
638 .setSmallIcon(R.drawable.vpn_connected)
Chia-chi Yeha4b87b52011-06-30 23:21:55 -0700639 .setLargeIcon(icon)
640 .setContentTitle(title)
Chia-chi Yehf8905fd2011-06-14 16:35:02 -0700641 .setContentText(text)
Jeff Sharkey899223b2012-08-04 15:24:58 -0700642 .setContentIntent(mStatusIntent)
Chia-chi Yeh50fe7092012-01-11 14:26:24 -0800643 .setDefaults(0)
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700644 .setOngoing(true)
Jeff Sharkey899223b2012-08-04 15:24:58 -0700645 .build();
Chad Brubakerc2865192013-07-10 14:46:23 -0700646 nm.notifyAsUser(null, R.drawable.vpn_connected, notification, new UserHandle(user));
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700647 }
648 }
649
Chad Brubakerc2865192013-07-10 14:46:23 -0700650 private void hideNotification(int user) {
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700651 if (!mEnableNotif) return;
Jeff Sharkey899223b2012-08-04 15:24:58 -0700652 mStatusIntent = null;
653
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700654 NotificationManager nm = (NotificationManager)
655 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
656
657 if (nm != null) {
Chad Brubakerc2865192013-07-10 14:46:23 -0700658 nm.cancelAsUser(null, R.drawable.vpn_connected, new UserHandle(user));
Chia-chi Yehff3bdca2011-05-23 17:26:46 -0700659 }
660 }
661
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700662 private native int jniCreate(int mtu);
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700663 private native String jniGetName(int tun);
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700664 private native int jniSetAddresses(String interfaze, String addresses);
665 private native int jniSetRoutes(String interfaze, String routes);
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -0700666 private native void jniReset(String interfaze);
667 private native int jniCheck(String interfaze);
668 private native void jniProtect(int socket, String interfaze);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700669
Lorenzo Colitti41fb98c2013-06-28 17:26:21 +0900670 private static RouteInfo findIPv4DefaultRoute(LinkProperties prop) {
671 for (RouteInfo route : prop.getAllRoutes()) {
Jeff Sharkey82f85212012-08-24 11:17:25 -0700672 // Currently legacy VPN only works on IPv4.
673 if (route.isDefaultRoute() && route.getGateway() instanceof Inet4Address) {
Lorenzo Colitti41fb98c2013-06-28 17:26:21 +0900674 return route;
Jeff Sharkey82f85212012-08-24 11:17:25 -0700675 }
676 }
Jeff Sharkey899223b2012-08-04 15:24:58 -0700677
Lorenzo Colitti41fb98c2013-06-28 17:26:21 +0900678 throw new IllegalStateException("Unable to find IPv4 default gateway");
Jeff Sharkey82f85212012-08-24 11:17:25 -0700679 }
680
681 /**
682 * Start legacy VPN, controlling native daemons as needed. Creates a
683 * secondary thread to perform connection work, returning quickly.
684 */
685 public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, LinkProperties egress) {
Robert Greenwalt5a6bdc42013-02-15 10:56:35 -0800686 enforceControlPermission();
Kenny Rootb9594ce2013-02-14 10:18:38 -0800687 if (!keyStore.isUnlocked()) {
Jeff Sharkey82f85212012-08-24 11:17:25 -0700688 throw new IllegalStateException("KeyStore isn't unlocked");
689 }
690
Lorenzo Colitti41fb98c2013-06-28 17:26:21 +0900691 final RouteInfo ipv4DefaultRoute = findIPv4DefaultRoute(egress);
692 final String gateway = ipv4DefaultRoute.getGateway().getHostAddress();
693 final String iface = ipv4DefaultRoute.getInterface();
Jeff Sharkey82f85212012-08-24 11:17:25 -0700694
695 // Load certificates.
696 String privateKey = "";
697 String userCert = "";
698 String caCert = "";
699 String serverCert = "";
700 if (!profile.ipsecUserCert.isEmpty()) {
701 privateKey = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert;
702 byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert);
Elliott Hughesd396a442013-06-28 16:24:48 -0700703 userCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
Jeff Sharkey82f85212012-08-24 11:17:25 -0700704 }
705 if (!profile.ipsecCaCert.isEmpty()) {
706 byte[] value = keyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert);
Elliott Hughesd396a442013-06-28 16:24:48 -0700707 caCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
Jeff Sharkey82f85212012-08-24 11:17:25 -0700708 }
709 if (!profile.ipsecServerCert.isEmpty()) {
710 byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecServerCert);
Elliott Hughesd396a442013-06-28 16:24:48 -0700711 serverCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
Jeff Sharkey82f85212012-08-24 11:17:25 -0700712 }
713 if (privateKey == null || userCert == null || caCert == null || serverCert == null) {
714 throw new IllegalStateException("Cannot load credentials");
715 }
716
717 // Prepare arguments for racoon.
718 String[] racoon = null;
719 switch (profile.type) {
720 case VpnProfile.TYPE_L2TP_IPSEC_PSK:
721 racoon = new String[] {
722 iface, profile.server, "udppsk", profile.ipsecIdentifier,
723 profile.ipsecSecret, "1701",
724 };
725 break;
726 case VpnProfile.TYPE_L2TP_IPSEC_RSA:
727 racoon = new String[] {
728 iface, profile.server, "udprsa", privateKey, userCert,
729 caCert, serverCert, "1701",
730 };
731 break;
732 case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
733 racoon = new String[] {
734 iface, profile.server, "xauthpsk", profile.ipsecIdentifier,
735 profile.ipsecSecret, profile.username, profile.password, "", gateway,
736 };
737 break;
738 case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
739 racoon = new String[] {
740 iface, profile.server, "xauthrsa", privateKey, userCert,
741 caCert, serverCert, profile.username, profile.password, "", gateway,
742 };
743 break;
744 case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
745 racoon = new String[] {
746 iface, profile.server, "hybridrsa",
747 caCert, serverCert, profile.username, profile.password, "", gateway,
748 };
749 break;
750 }
751
752 // Prepare arguments for mtpd.
753 String[] mtpd = null;
754 switch (profile.type) {
755 case VpnProfile.TYPE_PPTP:
756 mtpd = new String[] {
757 iface, "pptp", profile.server, "1723",
758 "name", profile.username, "password", profile.password,
759 "linkname", "vpn", "refuse-eap", "nodefaultroute",
760 "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
761 (profile.mppe ? "+mppe" : "nomppe"),
762 };
763 break;
764 case VpnProfile.TYPE_L2TP_IPSEC_PSK:
765 case VpnProfile.TYPE_L2TP_IPSEC_RSA:
766 mtpd = new String[] {
767 iface, "l2tp", profile.server, "1701", profile.l2tpSecret,
768 "name", profile.username, "password", profile.password,
769 "linkname", "vpn", "refuse-eap", "nodefaultroute",
770 "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
771 };
772 break;
773 }
774
775 VpnConfig config = new VpnConfig();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700776 config.legacy = true;
Jeff Sharkey82f85212012-08-24 11:17:25 -0700777 config.user = profile.key;
778 config.interfaze = iface;
779 config.session = profile.name;
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700780
781 config.addLegacyRoutes(profile.routes);
Jeff Sharkey82f85212012-08-24 11:17:25 -0700782 if (!profile.dnsServers.isEmpty()) {
783 config.dnsServers = Arrays.asList(profile.dnsServers.split(" +"));
784 }
785 if (!profile.searchDomains.isEmpty()) {
786 config.searchDomains = Arrays.asList(profile.searchDomains.split(" +"));
787 }
Jeff Sharkey82f85212012-08-24 11:17:25 -0700788 startLegacyVpn(config, racoon, mtpd);
789 }
790
791 private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
792 stopLegacyVpn();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700793
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700794 // Prepare for the new request. This also checks the caller.
795 prepare(null, VpnConfig.LEGACY_VPN);
Jeff Sharkey899223b2012-08-04 15:24:58 -0700796 updateState(DetailedState.CONNECTING, "startLegacyVpn");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700797
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700798 // Start a new LegacyVpnRunner and we are done!
Chia-chi Yeh100155a2011-07-03 16:52:38 -0700799 mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd);
800 mLegacyVpnRunner.start();
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700801 }
802
Jeff Sharkey899223b2012-08-04 15:24:58 -0700803 public synchronized void stopLegacyVpn() {
804 if (mLegacyVpnRunner != null) {
805 mLegacyVpnRunner.exit();
806 mLegacyVpnRunner = null;
807
808 synchronized (LegacyVpnRunner.TAG) {
809 // wait for old thread to completely finish before spinning up
810 // new instance, otherwise state updates can be out of order.
811 }
812 }
813 }
814
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700815 /**
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700816 * Return the information of the current ongoing legacy VPN.
817 */
818 public synchronized LegacyVpnInfo getLegacyVpnInfo() {
Chia-chi Yehdadc8572012-06-08 13:05:58 -0700819 // Check if the caller is authorized.
820 enforceControlPermission();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700821 if (mLegacyVpnRunner == null) return null;
822
823 final LegacyVpnInfo info = new LegacyVpnInfo();
Chad Brubakerc2865192013-07-10 14:46:23 -0700824 info.key = mConfig.user;
Jeff Sharkey899223b2012-08-04 15:24:58 -0700825 info.state = LegacyVpnInfo.stateFromNetworkInfo(mNetworkInfo);
826 if (mNetworkInfo.isConnected()) {
827 info.intent = mStatusIntent;
828 }
829 return info;
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700830 }
831
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700832 public VpnConfig getLegacyVpnConfig() {
833 if (mLegacyVpnRunner != null) {
Chad Brubakerc2865192013-07-10 14:46:23 -0700834 return mConfig;
Jeff Sharkey69ddab42012-08-25 00:05:46 -0700835 } else {
836 return null;
837 }
838 }
839
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700840 /**
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700841 * Bringing up a VPN connection takes time, and that is all this thread
842 * does. Here we have plenty of time. The only thing we need to take
843 * care of is responding to interruptions as soon as possible. Otherwise
844 * requests will be piled up. This can be done in a Handler as a state
845 * machine, but it is much easier to read in the current form.
846 */
847 private class LegacyVpnRunner extends Thread {
848 private static final String TAG = "LegacyVpnRunner";
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700849
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700850 private final String[] mDaemons;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700851 private final String[][] mArguments;
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700852 private final LocalSocket[] mSockets;
Robert Greenwalt53c04bd2012-10-12 17:02:45 -0700853 private final String mOuterInterface;
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -0700854 private final AtomicInteger mOuterConnection =
855 new AtomicInteger(ConnectivityManager.TYPE_NONE);
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700856
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700857 private long mTimer = -1;
858
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -0700859 /**
860 * Watch for the outer connection (passing in the constructor) going away.
861 */
862 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
863 @Override
864 public void onReceive(Context context, Intent intent) {
Jeff Sharkey57666932013-04-30 17:01:57 -0700865 if (!mEnableTeardown) return;
866
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -0700867 if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
868 if (intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE,
869 ConnectivityManager.TYPE_NONE) == mOuterConnection.get()) {
870 NetworkInfo info = (NetworkInfo)intent.getExtra(
871 ConnectivityManager.EXTRA_NETWORK_INFO);
872 if (info != null && !info.isConnectedOrConnecting()) {
873 try {
874 mObserver.interfaceStatusChanged(mOuterInterface, false);
875 } catch (RemoteException e) {}
876 }
877 }
878 }
879 }
880 };
881
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700882 public LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700883 super(TAG);
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700884 mConfig = config;
885 mDaemons = new String[] {"racoon", "mtpd"};
Jeff Sharkey899223b2012-08-04 15:24:58 -0700886 // TODO: clear arguments from memory once launched
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700887 mArguments = new String[][] {racoon, mtpd};
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700888 mSockets = new LocalSocket[mDaemons.length];
Robert Greenwalt53c04bd2012-10-12 17:02:45 -0700889
890 // This is the interface which VPN is running on,
891 // mConfig.interfaze will change to point to OUR
892 // internal interface soon. TODO - add inner/outer to mconfig
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -0700893 // TODO - we have a race - if the outer iface goes away/disconnects before we hit this
Chad Brubaker4ca19e82013-06-14 11:16:51 -0700894 // we will leave the VPN up. We should check that it's still there/connected after
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -0700895 // registering
Robert Greenwalt53c04bd2012-10-12 17:02:45 -0700896 mOuterInterface = mConfig.interfaze;
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -0700897
898 try {
899 mOuterConnection.set(
900 mConnService.findConnectionTypeForIface(mOuterInterface));
901 } catch (Exception e) {
902 mOuterConnection.set(ConnectivityManager.TYPE_NONE);
903 }
904
905 IntentFilter filter = new IntentFilter();
906 filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
907 mContext.registerReceiver(mBroadcastReceiver, filter);
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700908 }
909
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700910 public void check(String interfaze) {
Robert Greenwalt53c04bd2012-10-12 17:02:45 -0700911 if (interfaze.equals(mOuterInterface)) {
Chia-chi Yehaa1727f2011-07-14 18:55:33 -0700912 Log.i(TAG, "Legacy VPN is going down with " + interfaze);
913 exit();
914 }
915 }
916
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700917 public void exit() {
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700918 // We assume that everything is reset after stopping the daemons.
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700919 interrupt();
Chia-chi Yeh5317f032011-08-22 13:09:49 -0700920 for (LocalSocket socket : mSockets) {
Jeff Sharkey065b2992012-08-05 14:16:48 -0700921 IoUtils.closeQuietly(socket);
Chia-chi Yeh41d16852011-07-01 02:12:06 -0700922 }
Jeff Sharkey899223b2012-08-04 15:24:58 -0700923 updateState(DetailedState.DISCONNECTED, "exit");
Robert Greenwalt1b0ca9d2013-04-22 11:13:02 -0700924 try {
925 mContext.unregisterReceiver(mBroadcastReceiver);
926 } catch (IllegalArgumentException e) {}
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700927 }
928
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700929 @Override
930 public void run() {
931 // Wait for the previous thread since it has been interrupted.
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700932 Log.v(TAG, "Waiting");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700933 synchronized (TAG) {
Chia-chi Yeh2e467642011-07-04 03:23:12 -0700934 Log.v(TAG, "Executing");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700935 execute();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700936 monitorDaemons();
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700937 }
938 }
939
940 private void checkpoint(boolean yield) throws InterruptedException {
941 long now = SystemClock.elapsedRealtime();
942 if (mTimer == -1) {
943 mTimer = now;
944 Thread.sleep(1);
Chia-chi Yeh7ef86112011-07-22 15:46:52 -0700945 } else if (now - mTimer <= 60000) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700946 Thread.sleep(yield ? 200 : 1);
947 } else {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700948 updateState(DetailedState.FAILED, "checkpoint");
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700949 throw new IllegalStateException("Time is up");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700950 }
951 }
952
953 private void execute() {
954 // Catch all exceptions so we can clean up few things.
Jeff Sharkey899223b2012-08-04 15:24:58 -0700955 boolean initFinished = false;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700956 try {
957 // Initialize the timer.
958 checkpoint(false);
959
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700960 // Wait for the daemons to stop.
961 for (String daemon : mDaemons) {
Jeff Sharkey088f29f2012-08-05 14:55:04 -0700962 while (!SystemService.isStopped(daemon)) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700963 checkpoint(true);
964 }
965 }
966
Chia-chi Yeh97a61562011-07-14 15:05:05 -0700967 // Clear the previous state.
968 File state = new File("/data/misc/vpn/state");
969 state.delete();
970 if (state.exists()) {
971 throw new IllegalStateException("Cannot delete the state");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700972 }
Chia-chi Yehc1872732011-12-08 16:51:41 -0800973 new File("/data/misc/vpn/abort").delete();
Jeff Sharkey899223b2012-08-04 15:24:58 -0700974 initFinished = true;
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700975
Chia-chi Yehe9107902011-07-02 01:48:50 -0700976 // Check if we need to restart any of the daemons.
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700977 boolean restart = false;
978 for (String[] arguments : mArguments) {
979 restart = restart || (arguments != null);
980 }
981 if (!restart) {
Jeff Sharkey899223b2012-08-04 15:24:58 -0700982 updateState(DetailedState.DISCONNECTED, "execute");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700983 return;
984 }
Jeff Sharkey899223b2012-08-04 15:24:58 -0700985 updateState(DetailedState.CONNECTING, "execute");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700986
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700987 // Start the daemon with arguments.
988 for (int i = 0; i < mDaemons.length; ++i) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700989 String[] arguments = mArguments[i];
990 if (arguments == null) {
991 continue;
992 }
993
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700994 // Start the daemon.
995 String daemon = mDaemons[i];
Jeff Sharkey088f29f2012-08-05 14:55:04 -0700996 SystemService.start(daemon);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -0700997
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -0700998 // Wait for the daemon to start.
Jeff Sharkey088f29f2012-08-05 14:55:04 -0700999 while (!SystemService.isRunning(daemon)) {
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -07001000 checkpoint(true);
1001 }
1002
1003 // Create the control socket.
Chia-chi Yeh5317f032011-08-22 13:09:49 -07001004 mSockets[i] = new LocalSocket();
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -07001005 LocalSocketAddress address = new LocalSocketAddress(
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -07001006 daemon, LocalSocketAddress.Namespace.RESERVED);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -07001007
1008 // Wait for the socket to connect.
1009 while (true) {
1010 try {
Chia-chi Yeh5317f032011-08-22 13:09:49 -07001011 mSockets[i].connect(address);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -07001012 break;
1013 } catch (Exception e) {
1014 // ignore
1015 }
1016 checkpoint(true);
1017 }
Chia-chi Yeh5317f032011-08-22 13:09:49 -07001018 mSockets[i].setSoTimeout(500);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -07001019
1020 // Send over the arguments.
Chia-chi Yeh5317f032011-08-22 13:09:49 -07001021 OutputStream out = mSockets[i].getOutputStream();
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -07001022 for (String argument : arguments) {
Elliott Hughesd396a442013-06-28 16:24:48 -07001023 byte[] bytes = argument.getBytes(StandardCharsets.UTF_8);
Chia-chi Yeh5317f032011-08-22 13:09:49 -07001024 if (bytes.length >= 0xFFFF) {
Chia-chi Yeh97a61562011-07-14 15:05:05 -07001025 throw new IllegalArgumentException("Argument is too large");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -07001026 }
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -07001027 out.write(bytes.length >> 8);
1028 out.write(bytes.length);
1029 out.write(bytes);
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -07001030 checkpoint(false);
1031 }
Chia-chi Yeh5317f032011-08-22 13:09:49 -07001032 out.write(0xFF);
1033 out.write(0xFF);
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -07001034 out.flush();
Chia-chi Yeh97a61562011-07-14 15:05:05 -07001035
1036 // Wait for End-of-File.
Chia-chi Yeh5317f032011-08-22 13:09:49 -07001037 InputStream in = mSockets[i].getInputStream();
Chia-chi Yeh97a61562011-07-14 15:05:05 -07001038 while (true) {
1039 try {
1040 if (in.read() == -1) {
1041 break;
1042 }
1043 } catch (Exception e) {
1044 // ignore
1045 }
1046 checkpoint(true);
1047 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -07001048 }
1049
Chia-chi Yeh97a61562011-07-14 15:05:05 -07001050 // Wait for the daemons to create the new state.
1051 while (!state.exists()) {
Chia-chi Yeh1f7746b2011-07-01 00:29:06 -07001052 // Check if a running daemon is dead.
1053 for (int i = 0; i < mDaemons.length; ++i) {
1054 String daemon = mDaemons[i];
Jeff Sharkey088f29f2012-08-05 14:55:04 -07001055 if (mArguments[i] != null && !SystemService.isRunning(daemon)) {
Chia-chi Yeh2e467642011-07-04 03:23:12 -07001056 throw new IllegalStateException(daemon + " is dead");
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -07001057 }
1058 }
1059 checkpoint(true);
1060 }
1061
Chia-chi Yeh97a61562011-07-14 15:05:05 -07001062 // Now we are connected. Read and parse the new state.
Chia-chi Yehc1bac3a2011-12-16 15:00:31 -08001063 String[] parameters = FileUtils.readTextFile(state, 0, null).split("\n", -1);
Chia-chi Yeh97a61562011-07-14 15:05:05 -07001064 if (parameters.length != 6) {
1065 throw new IllegalStateException("Cannot parse the state");
1066 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -07001067
Chia-chi Yeh97a61562011-07-14 15:05:05 -07001068 // Set the interface and the addresses in the config.
1069 mConfig.interfaze = parameters[0].trim();
Chia-chi Yeh97a61562011-07-14 15:05:05 -07001070
Chad Brubaker4ca19e82013-06-14 11:16:51 -07001071 mConfig.addLegacyAddresses(parameters[1]);
Chia-chi Yeh97a61562011-07-14 15:05:05 -07001072 // Set the routes if they are not set in the config.
1073 if (mConfig.routes == null || mConfig.routes.isEmpty()) {
Chad Brubaker4ca19e82013-06-14 11:16:51 -07001074 mConfig.addLegacyRoutes(parameters[2]);
Chia-chi Yeh97a61562011-07-14 15:05:05 -07001075 }
1076
1077 // Set the DNS servers if they are not set in the config.
Chia-chi Yeh41d16852011-07-01 02:12:06 -07001078 if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
Chia-chi Yeh97a61562011-07-14 15:05:05 -07001079 String dnsServers = parameters[3].trim();
Chia-chi Yeh41d16852011-07-01 02:12:06 -07001080 if (!dnsServers.isEmpty()) {
1081 mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
1082 }
1083 }
1084
Chia-chi Yeh97a61562011-07-14 15:05:05 -07001085 // Set the search domains if they are not set in the config.
1086 if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) {
1087 String searchDomains = parameters[4].trim();
1088 if (!searchDomains.isEmpty()) {
1089 mConfig.searchDomains = Arrays.asList(searchDomains.split(" "));
1090 }
1091 }
Chia-chi Yehe9107902011-07-02 01:48:50 -07001092
Chia-chi Yeh97a61562011-07-14 15:05:05 -07001093 // Set the routes.
Chad Brubaker4ca19e82013-06-14 11:16:51 -07001094 long token = Binder.clearCallingIdentity();
1095 try {
1096 mCallback.setMarkedForwarding(mConfig.interfaze);
1097 mCallback.setRoutes(mConfig.interfaze, mConfig.routes);
1098 } finally {
1099 Binder.restoreCallingIdentity(token);
1100 }
Chia-chi Yeh97a61562011-07-14 15:05:05 -07001101
1102 // Here is the last step and it must be done synchronously.
Chia-chi Yeh41d16852011-07-01 02:12:06 -07001103 synchronized (Vpn.this) {
Vinit Deshapnde2b862e52013-10-02 11:50:39 -07001104 // Set the start time
1105 mConfig.startTime = SystemClock.elapsedRealtime();
1106
Chia-chi Yeh41d16852011-07-01 02:12:06 -07001107 // Check if the thread is interrupted while we are waiting.
1108 checkpoint(false);
1109
Chia-chi Yehe9107902011-07-02 01:48:50 -07001110 // Check if the interface is gone while we are waiting.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -07001111 if (jniCheck(mConfig.interfaze) == 0) {
Chia-chi Yeh34e78132011-07-03 03:07:07 -07001112 throw new IllegalStateException(mConfig.interfaze + " is gone");
Chia-chi Yeh41d16852011-07-01 02:12:06 -07001113 }
Chia-chi Yehe9107902011-07-02 01:48:50 -07001114
1115 // Now INetworkManagementEventObserver is watching our back.
Chia-chi Yehc2b8aa02011-07-03 18:00:47 -07001116 mInterface = mConfig.interfaze;
Chad Brubakerc2865192013-07-10 14:46:23 -07001117 mVpnUsers = new SparseBooleanArray();
Chad Brubaker4ca19e82013-06-14 11:16:51 -07001118
1119 token = Binder.clearCallingIdentity();
1120 try {
1121 mCallback.override(mInterface, mConfig.dnsServers, mConfig.searchDomains);
Chad Brubakerc2865192013-07-10 14:46:23 -07001122 addVpnUserLocked(mUserId);
Chad Brubaker4ca19e82013-06-14 11:16:51 -07001123 } finally {
1124 Binder.restoreCallingIdentity(token);
1125 }
Chia-chi Yeh2e467642011-07-04 03:23:12 -07001126
Chad Brubakerc2865192013-07-10 14:46:23 -07001127 // Assign all restircted users to this VPN
1128 // (Legacy VPNs are Owner only)
1129 UserManager mgr = UserManager.get(mContext);
1130 token = Binder.clearCallingIdentity();
1131 try {
1132 for (UserInfo user : mgr.getUsers()) {
1133 if (user.isRestricted()) {
1134 try {
1135 addVpnUserLocked(user.id);
1136 } catch (Exception e) {
1137 Log.wtf(TAG, "Failed to add user " + user.id
1138 + " to owner's VPN");
1139 }
1140 }
1141 }
1142 } finally {
1143 Binder.restoreCallingIdentity(token);
1144 }
Chia-chi Yeh2e467642011-07-04 03:23:12 -07001145 Log.i(TAG, "Connected!");
Jeff Sharkey899223b2012-08-04 15:24:58 -07001146 updateState(DetailedState.CONNECTED, "execute");
Chia-chi Yeh41d16852011-07-01 02:12:06 -07001147 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -07001148 } catch (Exception e) {
Chia-chi Yeh2e467642011-07-04 03:23:12 -07001149 Log.i(TAG, "Aborting", e);
Chad Brubakerc2865192013-07-10 14:46:23 -07001150 // make sure the routing is cleared
Chad Brubaker4ca19e82013-06-14 11:16:51 -07001151 try {
1152 mCallback.clearMarkedForwarding(mConfig.interfaze);
1153 } catch (Exception ignored) {
1154 }
Chia-chi Yehe9107902011-07-02 01:48:50 -07001155 exit();
Chia-chi Yeh2e467642011-07-04 03:23:12 -07001156 } finally {
Chia-chi Yeh5317f032011-08-22 13:09:49 -07001157 // Kill the daemons if they fail to stop.
Jeff Sharkey899223b2012-08-04 15:24:58 -07001158 if (!initFinished) {
Chia-chi Yeh5317f032011-08-22 13:09:49 -07001159 for (String daemon : mDaemons) {
Jeff Sharkey088f29f2012-08-05 14:55:04 -07001160 SystemService.stop(daemon);
Chia-chi Yeh5317f032011-08-22 13:09:49 -07001161 }
1162 }
1163
Chia-chi Yeh2e467642011-07-04 03:23:12 -07001164 // Do not leave an unstable state.
Jeff Sharkey899223b2012-08-04 15:24:58 -07001165 if (!initFinished || mNetworkInfo.getDetailedState() == DetailedState.CONNECTING) {
1166 updateState(DetailedState.FAILED, "execute");
Chia-chi Yeh2e467642011-07-04 03:23:12 -07001167 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -07001168 }
1169 }
Jeff Sharkey899223b2012-08-04 15:24:58 -07001170
1171 /**
1172 * Monitor the daemons we started, moving to disconnected state if the
1173 * underlying services fail.
1174 */
1175 private void monitorDaemons() {
1176 if (!mNetworkInfo.isConnected()) {
1177 return;
1178 }
1179
1180 try {
1181 while (true) {
1182 Thread.sleep(2000);
1183 for (int i = 0; i < mDaemons.length; i++) {
1184 if (mArguments[i] != null && SystemService.isStopped(mDaemons[i])) {
1185 return;
1186 }
1187 }
1188 }
1189 } catch (InterruptedException e) {
1190 Log.d(TAG, "interrupted during monitorDaemons(); stopping services");
1191 } finally {
1192 for (String daemon : mDaemons) {
1193 SystemService.stop(daemon);
1194 }
1195
1196 updateState(DetailedState.DISCONNECTED, "babysit");
1197 }
1198 }
Chia-chi Yeh85a7ce02011-06-29 16:05:58 -07001199 }
Chia-chi Yehff3bdca2011-05-23 17:26:46 -07001200}