Vpn.java revision 8cd33ed84e94036a5e1201485af7603dc6fb0d9b
1ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh/* 2ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh * Copyright (C) 2011 The Android Open Source Project 3ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh * 4ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh * Licensed under the Apache License, Version 2.0 (the "License"); 5ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh * you may not use this file except in compliance with the License. 6ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh * You may obtain a copy of the License at 7ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh * 8ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh * http://www.apache.org/licenses/LICENSE-2.0 9ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh * 10ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh * Unless required by applicable law or agreed to in writing, software 11ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh * distributed under the License is distributed on an "AS IS" BASIS, 12ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh * See the License for the specific language governing permissions and 14ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh * limitations under the License. 15ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh */ 16ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh 17ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yehpackage com.android.server.connectivity; 18ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh 19899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkeyimport static android.Manifest.permission.BIND_VPN_SERVICE; 20899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey 214ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubakerimport android.app.AppGlobals; 22ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yehimport android.app.Notification; 23ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yehimport android.app.NotificationManager; 24899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkeyimport android.app.PendingIntent; 251b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwaltimport android.content.BroadcastReceiver; 26199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yehimport android.content.ComponentName; 27ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yehimport android.content.Context; 28ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yehimport android.content.Intent; 291b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwaltimport android.content.IntentFilter; 30199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yehimport android.content.ServiceConnection; 31ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yehimport android.content.pm.ApplicationInfo; 32ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yehimport android.content.pm.PackageManager; 336bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensenimport android.content.pm.PackageManager.NameNotFoundException; 34199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yehimport android.content.pm.ResolveInfo; 35c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubakerimport android.content.pm.UserInfo; 36ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yehimport android.graphics.Bitmap; 37ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yehimport android.graphics.Canvas; 38ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yehimport android.graphics.drawable.Drawable; 39899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkeyimport android.net.BaseNetworkStateTracker; 40899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkeyimport android.net.ConnectivityManager; 411b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwaltimport android.net.IConnectivityManager; 42ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yehimport android.net.INetworkManagementEventObserver; 434ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubakerimport android.net.LinkAddress; 4482f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkeyimport android.net.LinkProperties; 4585a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yehimport android.net.LocalSocket; 4685a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yehimport android.net.LocalSocketAddress; 476bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensenimport android.net.NetworkAgent; 486bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensenimport android.net.NetworkCapabilities; 49899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkeyimport android.net.NetworkInfo; 506bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensenimport android.net.NetworkInfo.DetailedState; 518cd33ed84e94036a5e1201485af7603dc6fb0d9bSreeram Ramachandranimport android.net.NetworkMisc; 52c023453a2b79b338aea36b48fd610a099379d34cChad Brubakerimport android.net.NetworkUtils; 5382f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkeyimport android.net.RouteInfo; 546bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensenimport android.net.UidRange; 55ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yehimport android.os.Binder; 56c1bac3a6e240c1c9a14a7b515f585977fb908930Chia-chi Yehimport android.os.FileUtils; 57199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yehimport android.os.IBinder; 58899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkeyimport android.os.INetworkManagementService; 596bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensenimport android.os.Looper; 60199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yehimport android.os.Parcel; 61ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yehimport android.os.ParcelFileDescriptor; 6285a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yehimport android.os.Process; 63899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkeyimport android.os.RemoteException; 6485a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yehimport android.os.SystemClock; 65088f29f55eebc6862a4cb5dddeaefacf24f74d95Jeff Sharkeyimport android.os.SystemService; 6650cdf7c3069eb2cf82acbad73c322b7a5f3af4b1Dianne Hackbornimport android.os.UserHandle; 67c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubakerimport android.os.UserManager; 6882f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkeyimport android.security.Credentials; 6982f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkeyimport android.security.KeyStore; 70ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yehimport android.util.Log; 71c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubakerimport android.util.SparseBooleanArray; 72ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh 73c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubakerimport com.android.internal.annotations.GuardedBy; 74ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yehimport com.android.internal.R; 752e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yehimport com.android.internal.net.LegacyVpnInfo; 7604ba25c418bc4538e9dc0f047cfb9608d358f679Chia-chi Yehimport com.android.internal.net.VpnConfig; 7782f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkeyimport com.android.internal.net.VpnProfile; 78899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkeyimport com.android.server.net.BaseNetworkObserver; 79ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh 8097a61565ea95472e65899070e64853f8c147bb11Chia-chi Yehimport java.io.File; 816bbf39cf6b81222f32d2b66b8fa85d562e0ad71cJeff Davidsonimport java.io.IOException; 8297a61565ea95472e65899070e64853f8c147bb11Chia-chi Yehimport java.io.InputStream; 8385a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yehimport java.io.OutputStream; 84c023453a2b79b338aea36b48fd610a099379d34cChad Brubakerimport java.net.InetAddress; 8582f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkeyimport java.net.Inet4Address; 86d396a448b2e36e29598c954b64bfddef73f3fae0Elliott Hughesimport java.nio.charset.StandardCharsets; 876bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensenimport java.util.ArrayList; 8841d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yehimport java.util.Arrays; 896bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensenimport java.util.List; 901b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwaltimport java.util.concurrent.atomic.AtomicInteger; 9185a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 92065b299df4159602327977dd007cb2cd6b64ab20Jeff Sharkeyimport libcore.io.IoUtils; 93065b299df4159602327977dd007cb2cd6b64ab20Jeff Sharkey 94ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh/** 95ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh * @hide 96ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh */ 976bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensenpublic class Vpn { 986bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen private static final String NETWORKTYPE = "VPN"; 99899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey private static final String TAG = "Vpn"; 100899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey private static final boolean LOGD = true; 1016bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen 102899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey // TODO: create separate trackers for each unique VPN to support 103899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey // automated reconnection 104199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh 1056bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen private Context mContext; 1066bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen private NetworkInfo mNetworkInfo; 1076bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen private String mPackage; 1086bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen private int mOwnerUID; 109c2b8aa0b4c822b0e307f62131650f4a6ee89bb66Chia-chi Yeh private String mInterface; 110199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh private Connection mConnection; 11185a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh private LegacyVpnRunner mLegacyVpnRunner; 112899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey private PendingIntent mStatusIntent; 11357666934b4a161cc2fb77eef15d0b894aaf8f173Jeff Sharkey private volatile boolean mEnableNotif = true; 11457666934b4a161cc2fb77eef15d0b894aaf8f173Jeff Sharkey private volatile boolean mEnableTeardown = true; 1151b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt private final IConnectivityManager mConnService; 1166bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen private final INetworkManagementService mNetd; 117c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker private VpnConfig mConfig; 1186bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen private NetworkAgent mNetworkAgent; 1196bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen private final Looper mLooper; 1206bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen private final NetworkCapabilities mNetworkCapabilities; 121c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker 122c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker /* list of users using this VPN. */ 123c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker @GuardedBy("this") 1246bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen private List<UidRange> mVpnUsers = null; 125c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker private BroadcastReceiver mUserIntentReceiver = null; 126c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker 1274ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker private final int mUserId; 128ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh 1296bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen public Vpn(Looper looper, Context context, INetworkManagementService netService, 1304ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker IConnectivityManager connService, int userId) { 131ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh mContext = context; 1326bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mNetd = netService; 1331b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt mConnService = connService; 1344ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker mUserId = userId; 1356bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mLooper = looper; 1366bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen 1376bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mPackage = VpnConfig.LEGACY_VPN; 1386bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mOwnerUID = getAppUid(mPackage); 139899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey 140899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey try { 141899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey netService.registerObserver(mObserver); 142899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } catch (RemoteException e) { 143899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey Log.wtf(TAG, "Problem registering observer", e); 144899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 145c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker if (userId == UserHandle.USER_OWNER) { 146c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker // Owner's VPN also needs to handle restricted users 147c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker mUserIntentReceiver = new BroadcastReceiver() { 148c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker @Override 149c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker public void onReceive(Context context, Intent intent) { 150c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker final String action = intent.getAction(); 151c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 152c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker UserHandle.USER_NULL); 153c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker if (userId == UserHandle.USER_NULL) return; 154c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker 155c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker if (Intent.ACTION_USER_ADDED.equals(action)) { 156c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker onUserAdded(userId); 157c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } else if (Intent.ACTION_USER_REMOVED.equals(action)) { 158c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker onUserRemoved(userId); 159c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 160c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 161c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker }; 162c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker 163c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker IntentFilter intentFilter = new IntentFilter(); 164c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker intentFilter.addAction(Intent.ACTION_USER_ADDED); 165c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker intentFilter.addAction(Intent.ACTION_USER_REMOVED); 166c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker mContext.registerReceiverAsUser( 167c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null); 168c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 1696bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen 1706bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_VPN, 0, NETWORKTYPE, ""); 1716bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen // TODO: Copy metered attribute and bandwidths from physical transport, b/16207332 1726bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mNetworkCapabilities = new NetworkCapabilities(); 1736bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN); 1746bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN); 175899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 176899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey 17757666934b4a161cc2fb77eef15d0b894aaf8f173Jeff Sharkey /** 17857666934b4a161cc2fb77eef15d0b894aaf8f173Jeff Sharkey * Set if this object is responsible for showing its own notifications. When 17957666934b4a161cc2fb77eef15d0b894aaf8f173Jeff Sharkey * {@code false}, notifications are handled externally by someone else. 18057666934b4a161cc2fb77eef15d0b894aaf8f173Jeff Sharkey */ 18169ddab4575ff684c533c995e07ca15fe18543fc0Jeff Sharkey public void setEnableNotifications(boolean enableNotif) { 18269ddab4575ff684c533c995e07ca15fe18543fc0Jeff Sharkey mEnableNotif = enableNotif; 18369ddab4575ff684c533c995e07ca15fe18543fc0Jeff Sharkey } 18469ddab4575ff684c533c995e07ca15fe18543fc0Jeff Sharkey 18557666934b4a161cc2fb77eef15d0b894aaf8f173Jeff Sharkey /** 18657666934b4a161cc2fb77eef15d0b894aaf8f173Jeff Sharkey * Set if this object is responsible for watching for {@link NetworkInfo} 18757666934b4a161cc2fb77eef15d0b894aaf8f173Jeff Sharkey * teardown. When {@code false}, teardown is handled externally by someone 18857666934b4a161cc2fb77eef15d0b894aaf8f173Jeff Sharkey * else. 18957666934b4a161cc2fb77eef15d0b894aaf8f173Jeff Sharkey */ 19057666934b4a161cc2fb77eef15d0b894aaf8f173Jeff Sharkey public void setEnableTeardown(boolean enableTeardown) { 19157666934b4a161cc2fb77eef15d0b894aaf8f173Jeff Sharkey mEnableTeardown = enableTeardown; 19257666934b4a161cc2fb77eef15d0b894aaf8f173Jeff Sharkey } 19357666934b4a161cc2fb77eef15d0b894aaf8f173Jeff Sharkey 194899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey /** 195899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey * Update current state, dispaching event to listeners. 196899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey */ 197899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey private void updateState(DetailedState detailedState, String reason) { 198899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey if (LOGD) Log.d(TAG, "setting state=" + detailedState + ", reason=" + reason); 199899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey mNetworkInfo.setDetailedState(detailedState, reason, null); 2006bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen if (mNetworkAgent != null) { 2016bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mNetworkAgent.sendNetworkInfo(mNetworkInfo); 2026bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 203ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh } 204ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh 205ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh /** 206100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh * Prepare for a VPN application. This method is designed to solve 207100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh * race conditions. It first compares the current prepared package 208100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh * with {@code oldPackage}. If they are the same, the prepared 209100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh * package is revoked and replaced with {@code newPackage}. If 210100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh * {@code oldPackage} is {@code null}, the comparison is omitted. 211100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh * If {@code newPackage} is the same package or {@code null}, the 212100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh * revocation is omitted. This method returns {@code true} if the 213100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh * operation is succeeded. 214e9107901ae264de4ff5603d3cfc63a03ca4117d4Chia-chi Yeh * 215100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh * Legacy VPN is handled specially since it is not a real package. 216100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh * It uses {@link VpnConfig#LEGACY_VPN} as its package name, and 217100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh * it can be revoked by itself. 218100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh * 219100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh * @param oldPackage The package name of the old VPN application. 220100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh * @param newPackage The package name of the new VPN application. 221100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh * @return true if the operation is succeeded. 222ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh */ 223100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh public synchronized boolean prepare(String oldPackage, String newPackage) { 224100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh // Return false if the package does not match. 225c2b8aa0b4c822b0e307f62131650f4a6ee89bb66Chia-chi Yeh if (oldPackage != null && !oldPackage.equals(mPackage)) { 226100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh return false; 227100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh } 228100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh 229100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh // Return true if we do not need to revoke. 230100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh if (newPackage == null || 231c2b8aa0b4c822b0e307f62131650f4a6ee89bb66Chia-chi Yeh (newPackage.equals(mPackage) && !newPackage.equals(VpnConfig.LEGACY_VPN))) { 232100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh return true; 233ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh } 234ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh 235dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh // Check if the caller is authorized. 236dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh enforceControlPermission(); 2377b0b834c3d12564c44ac134879a6dbc70e74be6eChia-chi Yeh 238ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh // Reset the interface and hide the notification. 239c2b8aa0b4c822b0e307f62131650f4a6ee89bb66Chia-chi Yeh if (mInterface != null) { 2406bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen for (UidRange uidRange : mVpnUsers) { 2416bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen hideNotification(uidRange.getStartUser()); 242899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 2436bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen agentDisconnect(); 2444ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker jniReset(mInterface); 245c2b8aa0b4c822b0e307f62131650f4a6ee89bb66Chia-chi Yeh mInterface = null; 246c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker mVpnUsers = null; 247ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh } 248ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh 249fcc1b41b663c1a0cb551344c4a16a5ad9ce36d60Chia-chi Yeh // Revoke the connection or stop LegacyVpnRunner. 250199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh if (mConnection != null) { 251199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh try { 252199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh mConnection.mService.transact(IBinder.LAST_CALL_TRANSACTION, 253199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh Parcel.obtain(), null, IBinder.FLAG_ONEWAY); 254199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh } catch (Exception e) { 255199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh // ignore 256199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh } 257199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh mContext.unbindService(mConnection); 258199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh mConnection = null; 259e9107901ae264de4ff5603d3cfc63a03ca4117d4Chia-chi Yeh } else if (mLegacyVpnRunner != null) { 26041d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh mLegacyVpnRunner.exit(); 26141d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh mLegacyVpnRunner = null; 26241d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh } 26341d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh 2646bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen long token = Binder.clearCallingIdentity(); 2656bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen try { 2666bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mNetd.denyProtect(mOwnerUID); 2676bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } catch (Exception e) { 2686bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen Log.wtf(TAG, "Failed to disallow UID " + mOwnerUID + " to call protect() " + e); 2696bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } finally { 2706bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen Binder.restoreCallingIdentity(token); 2716bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 2726bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen 273c2b8aa0b4c822b0e307f62131650f4a6ee89bb66Chia-chi Yeh Log.i(TAG, "Switched from " + mPackage + " to " + newPackage); 274c2b8aa0b4c822b0e307f62131650f4a6ee89bb66Chia-chi Yeh mPackage = newPackage; 2756bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mOwnerUID = getAppUid(newPackage); 2766bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen token = Binder.clearCallingIdentity(); 2776bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen try { 2786bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mNetd.allowProtect(mOwnerUID); 2796bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } catch (Exception e) { 2806bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen Log.wtf(TAG, "Failed to allow UID " + mOwnerUID + " to call protect() " + e); 2816bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } finally { 2826bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen Binder.restoreCallingIdentity(token); 2836bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 284c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker mConfig = null; 285899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey updateState(DetailedState.IDLE, "prepare"); 286100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh return true; 287ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh } 288ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh 2896bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen private int getAppUid(String app) { 2906bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen if (app == VpnConfig.LEGACY_VPN) { 2916bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen return Process.myUid(); 2926bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 293fcc1b41b663c1a0cb551344c4a16a5ad9ce36d60Chia-chi Yeh PackageManager pm = mContext.getPackageManager(); 2946bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen int result; 2956bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen try { 2966bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen result = pm.getPackageUid(app, mUserId); 2976bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } catch (NameNotFoundException e) { 2986bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen result = -1; 299fcc1b41b663c1a0cb551344c4a16a5ad9ce36d60Chia-chi Yeh } 3006bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen return result; 3016bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 3026bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen 3036bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen public NetworkInfo getNetworkInfo() { 3046bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen return mNetworkInfo; 3056bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 3066bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen 3076bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen private void agentConnect() { 3086bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen LinkProperties lp = new LinkProperties(); 3096bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen lp.setInterfaceName(mInterface); 3106bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen boolean hasDefaultRoute = false; 3116bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen for (RouteInfo route : mConfig.routes) { 3126bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen lp.addRoute(route); 3136bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen if (route.isDefaultRoute()) hasDefaultRoute = true; 3146bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 3156bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen if (hasDefaultRoute) { 3166bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); 3176bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } else { 3186bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); 3196bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 3206bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen if (mConfig.dnsServers != null) { 3216bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen for (String dnsServer : mConfig.dnsServers) { 3226bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen lp.addDnsServer(InetAddress.parseNumericAddress(dnsServer)); 3236bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 3246bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 3256bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen // Concatenate search domains into a string. 3266bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen StringBuilder buffer = new StringBuilder(); 3276bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen if (mConfig.searchDomains != null) { 3286bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen for (String domain : mConfig.searchDomains) { 3296bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen buffer.append(domain).append(' '); 3306bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 3316bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 3326bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen lp.setDomains(buffer.toString().trim()); 3336bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mNetworkInfo.setIsAvailable(true); 3346bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null); 3358cd33ed84e94036a5e1201485af7603dc6fb0d9bSreeram Ramachandran NetworkMisc networkMisc = new NetworkMisc(); 3368cd33ed84e94036a5e1201485af7603dc6fb0d9bSreeram Ramachandran if (mConfig.allowBypass) { 3378cd33ed84e94036a5e1201485af7603dc6fb0d9bSreeram Ramachandran networkMisc.allowBypass = true; 3388cd33ed84e94036a5e1201485af7603dc6fb0d9bSreeram Ramachandran } 3396bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen long token = Binder.clearCallingIdentity(); 3404ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker try { 3416bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE, 3428cd33ed84e94036a5e1201485af7603dc6fb0d9bSreeram Ramachandran mNetworkInfo, mNetworkCapabilities, lp, 0, networkMisc) { 3436bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen public void unwanted() { 3446bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen // We are user controlled, not driven by NetworkRequest. 3456bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen }; 3466bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen }; 3474ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker } finally { 3484ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker Binder.restoreCallingIdentity(token); 3494ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker } 3506bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen addVpnUserLocked(mUserId); 3516bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen // If we are owner assign all Restricted Users to this VPN 3526bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen if (mUserId == UserHandle.USER_OWNER) { 3536bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen token = Binder.clearCallingIdentity(); 3546bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen List<UserInfo> users; 3556bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen try { 3566bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen users = UserManager.get(mContext).getUsers(); 3576bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } finally { 3586bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen Binder.restoreCallingIdentity(token); 3596bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 3606bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen for (UserInfo user : users) { 3616bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen if (user.isRestricted()) { 3626bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen addVpnUserLocked(user.id); 3636bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 3646bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 3656bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 3666bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mNetworkAgent.addUidRanges(mVpnUsers.toArray(new UidRange[mVpnUsers.size()])); 3676bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 3686bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen 3696bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen private void agentDisconnect(NetworkInfo networkInfo, NetworkAgent networkAgent) { 3706bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen networkInfo.setIsAvailable(false); 3716bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen networkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null); 3726bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen if (networkAgent != null) { 3736bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen networkAgent.sendNetworkInfo(networkInfo); 3746bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 3756bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 3766bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen 3776bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen private void agentDisconnect(NetworkAgent networkAgent) { 3786bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen NetworkInfo networkInfo = new NetworkInfo(mNetworkInfo); 3796bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen agentDisconnect(networkInfo, networkAgent); 3806bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 3814ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker 3826bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen private void agentDisconnect() { 3836bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen if (mNetworkInfo.isConnected()) { 3846bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen agentDisconnect(mNetworkInfo, mNetworkAgent); 3856bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mNetworkAgent = null; 3866bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 387fcc1b41b663c1a0cb551344c4a16a5ad9ce36d60Chia-chi Yeh } 388fcc1b41b663c1a0cb551344c4a16a5ad9ce36d60Chia-chi Yeh 389fcc1b41b663c1a0cb551344c4a16a5ad9ce36d60Chia-chi Yeh /** 390e9107901ae264de4ff5603d3cfc63a03ca4117d4Chia-chi Yeh * Establish a VPN network and return the file descriptor of the VPN 391e9107901ae264de4ff5603d3cfc63a03ca4117d4Chia-chi Yeh * interface. This methods returns {@code null} if the application is 392100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh * revoked or not prepared. 393ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh * 394e9107901ae264de4ff5603d3cfc63a03ca4117d4Chia-chi Yeh * @param config The parameters to configure the network. 395e9107901ae264de4ff5603d3cfc63a03ca4117d4Chia-chi Yeh * @return The file descriptor of the VPN interface. 396ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh */ 39704ba25c418bc4538e9dc0f047cfb9608d358f679Chia-chi Yeh public synchronized ParcelFileDescriptor establish(VpnConfig config) { 398ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh // Check if the caller is already prepared. 399c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker UserManager mgr = UserManager.get(mContext); 4006bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen if (Binder.getCallingUid() != mOwnerUID) { 4017b0b834c3d12564c44ac134879a6dbc70e74be6eChia-chi Yeh return null; 402ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh } 403fcc1b41b663c1a0cb551344c4a16a5ad9ce36d60Chia-chi Yeh // Check if the service is properly declared. 404199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh Intent intent = new Intent(VpnConfig.SERVICE_INTERFACE); 405199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh intent.setClassName(mPackage, config.user); 4064ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker long token = Binder.clearCallingIdentity(); 4074ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker try { 408c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker // Restricted users are not allowed to create VPNs, they are tied to Owner 409c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker UserInfo user = mgr.getUserInfo(mUserId); 410f5116d01b20f21ba32cd9eaa3412daf97f41c623Julia Reynolds if (user.isRestricted() || mgr.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) { 411c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker throw new SecurityException("Restricted users cannot establish VPNs"); 412c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 413c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker 4144ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker ResolveInfo info = AppGlobals.getPackageManager().resolveService(intent, 4154ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker null, 0, mUserId); 4164ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker if (info == null) { 4174ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker throw new SecurityException("Cannot find " + config.user); 4184ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker } 4194ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker if (!BIND_VPN_SERVICE.equals(info.serviceInfo.permission)) { 4204ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker throw new SecurityException(config.user + " does not require " + BIND_VPN_SERVICE); 4214ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker } 4224ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker } catch (RemoteException e) { 4234ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker throw new SecurityException("Cannot find " + config.user); 4244ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker } finally { 4254ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker Binder.restoreCallingIdentity(token); 426199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh } 427fcc1b41b663c1a0cb551344c4a16a5ad9ce36d60Chia-chi Yeh 4284c5c33e5e6fb37f85977c70a0baba4e1ed51fe0dChad Brubaker // Save the old config in case we need to go back. 4294c5c33e5e6fb37f85977c70a0baba4e1ed51fe0dChad Brubaker VpnConfig oldConfig = mConfig; 4304c5c33e5e6fb37f85977c70a0baba4e1ed51fe0dChad Brubaker String oldInterface = mInterface; 4314c5c33e5e6fb37f85977c70a0baba4e1ed51fe0dChad Brubaker Connection oldConnection = mConnection; 4326bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen NetworkAgent oldNetworkAgent = mNetworkAgent; 4336bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mNetworkAgent = null; 4346bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen List<UidRange> oldUsers = mVpnUsers; 4354c5c33e5e6fb37f85977c70a0baba4e1ed51fe0dChad Brubaker 436e9107901ae264de4ff5603d3cfc63a03ca4117d4Chia-chi Yeh // Configure the interface. Abort if any of these steps fails. 43797a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu)); 438ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh try { 439899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey updateState(DetailedState.CONNECTING, "establish"); 440c2b8aa0b4c822b0e307f62131650f4a6ee89bb66Chia-chi Yeh String interfaze = jniGetName(tun.getFd()); 4414ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker 442c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker // TEMP use the old jni calls until there is support for netd address setting 4434ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker StringBuilder builder = new StringBuilder(); 4444ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker for (LinkAddress address : config.addresses) { 4454ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker builder.append(" " + address); 44697a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh } 4474ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker if (jniSetAddresses(interfaze, builder.toString()) < 1) { 4484ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker throw new IllegalArgumentException("At least one address must be specified"); 44997a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh } 450199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh Connection connection = new Connection(); 4514ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker if (!mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE, 4524ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker new UserHandle(mUserId))) { 453199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh throw new IllegalStateException("Cannot bind " + config.user); 454199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh } 4554c5c33e5e6fb37f85977c70a0baba4e1ed51fe0dChad Brubaker 456199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh mConnection = connection; 457c2b8aa0b4c822b0e307f62131650f4a6ee89bb66Chia-chi Yeh mInterface = interfaze; 4584ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker 4594ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker // Fill more values. 4604ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker config.user = mPackage; 4614ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker config.interfaze = mInterface; 462c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker config.startTime = SystemClock.elapsedRealtime(); 463c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker mConfig = config; 4644c5c33e5e6fb37f85977c70a0baba4e1ed51fe0dChad Brubaker 4654ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker // Set up forwarding and DNS rules. 4666bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mVpnUsers = new ArrayList<UidRange>(); 4676bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen agentConnect(); 4684ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker 4694c5c33e5e6fb37f85977c70a0baba4e1ed51fe0dChad Brubaker if (oldConnection != null) { 4704c5c33e5e6fb37f85977c70a0baba4e1ed51fe0dChad Brubaker mContext.unbindService(oldConnection); 4714c5c33e5e6fb37f85977c70a0baba4e1ed51fe0dChad Brubaker } 4726bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen // Remove the old tun's user forwarding rules 4736bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen // The new tun's user rules have already been added so they will take over 4746bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen // as rules are deleted. This prevents data leakage as the rules are moved over. 4756bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen agentDisconnect(oldNetworkAgent); 4764c5c33e5e6fb37f85977c70a0baba4e1ed51fe0dChad Brubaker if (oldInterface != null && !oldInterface.equals(interfaze)) { 4774c5c33e5e6fb37f85977c70a0baba4e1ed51fe0dChad Brubaker jniReset(oldInterface); 4784c5c33e5e6fb37f85977c70a0baba4e1ed51fe0dChad Brubaker } 4796bbf39cf6b81222f32d2b66b8fa85d562e0ad71cJeff Davidson 4806bbf39cf6b81222f32d2b66b8fa85d562e0ad71cJeff Davidson try { 4816bbf39cf6b81222f32d2b66b8fa85d562e0ad71cJeff Davidson IoUtils.setBlocking(tun.getFileDescriptor(), config.blocking); 4826bbf39cf6b81222f32d2b66b8fa85d562e0ad71cJeff Davidson } catch (IOException e) { 4836bbf39cf6b81222f32d2b66b8fa85d562e0ad71cJeff Davidson throw new IllegalStateException( 4846bbf39cf6b81222f32d2b66b8fa85d562e0ad71cJeff Davidson "Cannot set tunnel's fd as blocking=" + config.blocking, e); 4856bbf39cf6b81222f32d2b66b8fa85d562e0ad71cJeff Davidson } 486ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh } catch (RuntimeException e) { 487065b299df4159602327977dd007cb2cd6b64ab20Jeff Sharkey IoUtils.closeQuietly(tun); 4886bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen agentDisconnect(); 4894c5c33e5e6fb37f85977c70a0baba4e1ed51fe0dChad Brubaker // restore old state 4904c5c33e5e6fb37f85977c70a0baba4e1ed51fe0dChad Brubaker mConfig = oldConfig; 4914c5c33e5e6fb37f85977c70a0baba4e1ed51fe0dChad Brubaker mConnection = oldConnection; 4924c5c33e5e6fb37f85977c70a0baba4e1ed51fe0dChad Brubaker mVpnUsers = oldUsers; 4936bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mNetworkAgent = oldNetworkAgent; 4944c5c33e5e6fb37f85977c70a0baba4e1ed51fe0dChad Brubaker mInterface = oldInterface; 495ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh throw e; 496ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh } 497199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh Log.i(TAG, "Established by " + config.user + " on " + mInterface); 498ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh 499899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey // TODO: ensure that contract class eventually marks as connected 500899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey updateState(DetailedState.AUTHENTICATING, "establish"); 501c2b8aa0b4c822b0e307f62131650f4a6ee89bb66Chia-chi Yeh return tun; 502ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh } 503ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh 504c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker private boolean isRunningLocked() { 505c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker return mVpnUsers != null; 506c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 507c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker 5086bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen // Note: This function adds to mVpnUsers but does not publish list to NetworkAgent. 509c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker private void addVpnUserLocked(int user) { 510c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker if (!isRunningLocked()) { 511c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker throw new IllegalStateException("VPN is not active"); 512c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 51369887e838814642a7ae78fc810656c7c8afc2a19Robert Greenwalt 514c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker // add the user 5156bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mVpnUsers.add(UidRange.createForUser(user)); 516c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker 517c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker // show the notification 518c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker if (!mPackage.equals(VpnConfig.LEGACY_VPN)) { 519c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker // Load everything for the user's notification 520c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker PackageManager pm = mContext.getPackageManager(); 521c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker ApplicationInfo app = null; 5226bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen final long token = Binder.clearCallingIdentity(); 523c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker try { 524c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker app = AppGlobals.getPackageManager().getApplicationInfo(mPackage, 0, mUserId); 525c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } catch (RemoteException e) { 526c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker throw new IllegalStateException("Invalid application"); 5276bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } finally { 5286bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen Binder.restoreCallingIdentity(token); 529c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 530c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker String label = app.loadLabel(pm).toString(); 531c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker // Load the icon and convert it into a bitmap. 532c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker Drawable icon = app.loadIcon(pm); 533c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker Bitmap bitmap = null; 534c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) { 535c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker int width = mContext.getResources().getDimensionPixelSize( 536c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker android.R.dimen.notification_large_icon_width); 537c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker int height = mContext.getResources().getDimensionPixelSize( 538c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker android.R.dimen.notification_large_icon_height); 539c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker icon.setBounds(0, 0, width, height); 540c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 541c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker Canvas c = new Canvas(bitmap); 542c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker icon.draw(c); 543c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker c.setBitmap(null); 544c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 545c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker showNotification(label, bitmap, user); 546c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } else { 547c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker showNotification(null, null, user); 548c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 549c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 550c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker 551c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker private void removeVpnUserLocked(int user) { 552c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker if (!isRunningLocked()) { 553c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker throw new IllegalStateException("VPN is not active"); 554c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 5556bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen UidRange uidRange = UidRange.createForUser(user); 5566bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen if (mNetworkAgent != null) { 5576bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mNetworkAgent.removeUidRanges(new UidRange[] { uidRange }); 5586bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 5596bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mVpnUsers.remove(uidRange); 560c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker hideNotification(user); 561c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 562c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker 563c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker private void onUserAdded(int userId) { 564c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker // If the user is restricted tie them to the owner's VPN 565c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker synchronized(Vpn.this) { 566c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker UserManager mgr = UserManager.get(mContext); 567c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker UserInfo user = mgr.getUserInfo(userId); 568c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker if (user.isRestricted()) { 569c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker try { 570c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker addVpnUserLocked(userId); 5716bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen if (mNetworkAgent != null) { 5726bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen UidRange uidRange = UidRange.createForUser(userId); 5736bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mNetworkAgent.addUidRanges(new UidRange[] { uidRange }); 5746bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 575c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } catch (Exception e) { 576c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker Log.wtf(TAG, "Failed to add restricted user to owner", e); 577c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 578c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 579c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 580c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 581c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker 582c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker private void onUserRemoved(int userId) { 583c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker // clean up if restricted 584c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker synchronized(Vpn.this) { 585c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker UserManager mgr = UserManager.get(mContext); 586c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker UserInfo user = mgr.getUserInfo(userId); 587c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker if (user.isRestricted()) { 588c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker try { 589c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker removeVpnUserLocked(userId); 590c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } catch (Exception e) { 591c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker Log.wtf(TAG, "Failed to remove restricted user to owner", e); 592c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 593c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 594c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 595c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker } 596c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker 597bf6ff2c025405a3af496fe558dfc4468a9b45cc8Chad Brubaker /** 598bf6ff2c025405a3af496fe558dfc4468a9b45cc8Chad Brubaker * Return the configuration of the currently running VPN. 599bf6ff2c025405a3af496fe558dfc4468a9b45cc8Chad Brubaker */ 600bf6ff2c025405a3af496fe558dfc4468a9b45cc8Chad Brubaker public VpnConfig getVpnConfig() { 601bf6ff2c025405a3af496fe558dfc4468a9b45cc8Chad Brubaker enforceControlPermission(); 602bf6ff2c025405a3af496fe558dfc4468a9b45cc8Chad Brubaker return mConfig; 603bf6ff2c025405a3af496fe558dfc4468a9b45cc8Chad Brubaker } 604bf6ff2c025405a3af496fe558dfc4468a9b45cc8Chad Brubaker 605899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey @Deprecated 606899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey public synchronized void interfaceStatusChanged(String iface, boolean up) { 607899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey try { 608899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey mObserver.interfaceStatusChanged(iface, up); 609899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } catch (RemoteException e) { 610899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey // ignored; target is local 611aa1727fe0cbb902c5f53a3fae601b4e15da0a2f4Chia-chi Yeh } 612ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh } 613ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh 614899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey private INetworkManagementEventObserver mObserver = new BaseNetworkObserver() { 615899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey @Override 616899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey public void interfaceStatusChanged(String interfaze, boolean up) { 617899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey synchronized (Vpn.this) { 618899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey if (!up && mLegacyVpnRunner != null) { 619899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey mLegacyVpnRunner.check(interfaze); 620899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 621199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh } 622ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh } 623ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh 624899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey @Override 625899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey public void interfaceRemoved(String interfaze) { 626899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey synchronized (Vpn.this) { 627899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) { 6286bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen for (UidRange uidRange : mVpnUsers) { 6296bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen hideNotification(uidRange.getStartUser()); 630899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 6316bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mVpnUsers = null; 632899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey mInterface = null; 633899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey if (mConnection != null) { 634899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey mContext.unbindService(mConnection); 635899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey mConnection = null; 6366bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen agentDisconnect(); 637899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } else if (mLegacyVpnRunner != null) { 638899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey mLegacyVpnRunner.exit(); 639899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey mLegacyVpnRunner = null; 640899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 641899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 642899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 643899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 644899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey }; 645db3c8678e5cbdfec011afaf25bde2091152c30adHaoyu Bai 646dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh private void enforceControlPermission() { 647dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh // System user is allowed to control VPN. 648dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh if (Binder.getCallingUid() == Process.SYSTEM_UID) { 649dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh return; 650dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh } 6514ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker int appId = UserHandle.getAppId(Binder.getCallingUid()); 6524ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker final long token = Binder.clearCallingIdentity(); 653dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh try { 654212a195f008688c5483bf369d5a96753d582a24bNick Kralevich // System VPN dialogs are also allowed to control VPN. 655dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh PackageManager pm = mContext.getPackageManager(); 656dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh ApplicationInfo app = pm.getApplicationInfo(VpnConfig.DIALOGS_PACKAGE, 0); 657212a195f008688c5483bf369d5a96753d582a24bNick Kralevich if (((app.flags & ApplicationInfo.FLAG_SYSTEM) != 0) && (appId == app.uid)) { 658dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh return; 659dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh } 660dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh } catch (Exception e) { 661dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh // ignore 6624ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker } finally { 6634ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker Binder.restoreCallingIdentity(token); 664dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh } 665dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh 666dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh throw new SecurityException("Unauthorized Caller"); 667dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh } 668dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh 669199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh private class Connection implements ServiceConnection { 670199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh private IBinder mService; 671199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh 672199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh @Override 673199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh public void onServiceConnected(ComponentName name, IBinder service) { 674199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh mService = service; 675199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh } 676199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh 677199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh @Override 678199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh public void onServiceDisconnected(ComponentName name) { 679199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh mService = null; 680199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh } 681199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh } 682199ed6ef89bd356895534ba09ac43ed340cd9a1aChia-chi Yeh 683c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker private void showNotification(String label, Bitmap icon, int user) { 68469ddab4575ff684c533c995e07ca15fe18543fc0Jeff Sharkey if (!mEnableNotif) return; 6856bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen final long token = Binder.clearCallingIdentity(); 6866bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen try { 6876bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext); 6886bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen 6896bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen NotificationManager nm = (NotificationManager) 6906bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mContext.getSystemService(Context.NOTIFICATION_SERVICE); 6916bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen 6926bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen if (nm != null) { 6936bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen String title = (label == null) ? mContext.getString(R.string.vpn_title) : 6946bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mContext.getString(R.string.vpn_title_long, label); 6956bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen String text = (mConfig.session == null) ? mContext.getString(R.string.vpn_text) : 6966bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mContext.getString(R.string.vpn_text_long, mConfig.session); 6976bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen 6986bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen Notification notification = new Notification.Builder(mContext) 6996bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen .setSmallIcon(R.drawable.vpn_connected) 7006bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen .setLargeIcon(icon) 7016bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen .setContentTitle(title) 7026bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen .setContentText(text) 7036bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen .setContentIntent(mStatusIntent) 7046bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen .setDefaults(0) 7056bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen .setOngoing(true) 7066bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen .build(); 7076bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen nm.notifyAsUser(null, R.drawable.vpn_connected, notification, new UserHandle(user)); 7086bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 7096bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } finally { 7106bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen Binder.restoreCallingIdentity(token); 711ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh } 712ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh } 713ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh 714c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker private void hideNotification(int user) { 71569ddab4575ff684c533c995e07ca15fe18543fc0Jeff Sharkey if (!mEnableNotif) return; 716899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey mStatusIntent = null; 717899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey 718ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh NotificationManager nm = (NotificationManager) 719ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh mContext.getSystemService(Context.NOTIFICATION_SERVICE); 720ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh 721ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh if (nm != null) { 7226bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen final long token = Binder.clearCallingIdentity(); 7236bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen try { 7246bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen nm.cancelAsUser(null, R.drawable.vpn_connected, new UserHandle(user)); 7256bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } finally { 7266bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen Binder.restoreCallingIdentity(token); 7276bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen } 728ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh } 729ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh } 730ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh 73197a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh private native int jniCreate(int mtu); 732c2b8aa0b4c822b0e307f62131650f4a6ee89bb66Chia-chi Yeh private native String jniGetName(int tun); 73397a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh private native int jniSetAddresses(String interfaze, String addresses); 734c2b8aa0b4c822b0e307f62131650f4a6ee89bb66Chia-chi Yeh private native void jniReset(String interfaze); 735c2b8aa0b4c822b0e307f62131650f4a6ee89bb66Chia-chi Yeh private native int jniCheck(String interfaze); 73685a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 73741fb98c8681cbe0040b8d4efc65d33717c3819f6Lorenzo Colitti private static RouteInfo findIPv4DefaultRoute(LinkProperties prop) { 73841fb98c8681cbe0040b8d4efc65d33717c3819f6Lorenzo Colitti for (RouteInfo route : prop.getAllRoutes()) { 73982f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey // Currently legacy VPN only works on IPv4. 74082f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey if (route.isDefaultRoute() && route.getGateway() instanceof Inet4Address) { 74141fb98c8681cbe0040b8d4efc65d33717c3819f6Lorenzo Colitti return route; 74282f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey } 74382f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey } 74482f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey 74541fb98c8681cbe0040b8d4efc65d33717c3819f6Lorenzo Colitti throw new IllegalStateException("Unable to find IPv4 default gateway"); 74682f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey } 74782f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey 74885a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh /** 74982f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey * Start legacy VPN, controlling native daemons as needed. Creates a 75082f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey * secondary thread to perform connection work, returning quickly. 75185a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh */ 75282f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, LinkProperties egress) { 7535a6bdc46e2fdc8cfd930396773dd89efd19fa1f1Robert Greenwalt enforceControlPermission(); 754b9594ce9ebb3f5f303a280f04312ae5754ce3560Kenny Root if (!keyStore.isUnlocked()) { 75582f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey throw new IllegalStateException("KeyStore isn't unlocked"); 75682f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey } 757f5116d01b20f21ba32cd9eaa3412daf97f41c623Julia Reynolds UserManager mgr = UserManager.get(mContext); 758f5116d01b20f21ba32cd9eaa3412daf97f41c623Julia Reynolds UserInfo user = mgr.getUserInfo(mUserId); 759f5116d01b20f21ba32cd9eaa3412daf97f41c623Julia Reynolds if (user.isRestricted() || mgr.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) { 760f5116d01b20f21ba32cd9eaa3412daf97f41c623Julia Reynolds throw new SecurityException("Restricted users cannot establish VPNs"); 761f5116d01b20f21ba32cd9eaa3412daf97f41c623Julia Reynolds } 76282f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey 76341fb98c8681cbe0040b8d4efc65d33717c3819f6Lorenzo Colitti final RouteInfo ipv4DefaultRoute = findIPv4DefaultRoute(egress); 76441fb98c8681cbe0040b8d4efc65d33717c3819f6Lorenzo Colitti final String gateway = ipv4DefaultRoute.getGateway().getHostAddress(); 76541fb98c8681cbe0040b8d4efc65d33717c3819f6Lorenzo Colitti final String iface = ipv4DefaultRoute.getInterface(); 76682f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey 76782f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey // Load certificates. 76882f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey String privateKey = ""; 76982f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey String userCert = ""; 77082f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey String caCert = ""; 77182f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey String serverCert = ""; 77282f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey if (!profile.ipsecUserCert.isEmpty()) { 77382f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey privateKey = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert; 77482f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert); 775d396a448b2e36e29598c954b64bfddef73f3fae0Elliott Hughes userCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8); 77682f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey } 77782f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey if (!profile.ipsecCaCert.isEmpty()) { 77882f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey byte[] value = keyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert); 779d396a448b2e36e29598c954b64bfddef73f3fae0Elliott Hughes caCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8); 78082f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey } 78182f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey if (!profile.ipsecServerCert.isEmpty()) { 78282f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecServerCert); 783d396a448b2e36e29598c954b64bfddef73f3fae0Elliott Hughes serverCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8); 78482f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey } 78582f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey if (privateKey == null || userCert == null || caCert == null || serverCert == null) { 78682f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey throw new IllegalStateException("Cannot load credentials"); 78782f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey } 78882f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey 78982f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey // Prepare arguments for racoon. 79082f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey String[] racoon = null; 79182f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey switch (profile.type) { 79282f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey case VpnProfile.TYPE_L2TP_IPSEC_PSK: 79382f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey racoon = new String[] { 79482f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey iface, profile.server, "udppsk", profile.ipsecIdentifier, 79582f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey profile.ipsecSecret, "1701", 79682f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey }; 79782f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey break; 79882f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey case VpnProfile.TYPE_L2TP_IPSEC_RSA: 79982f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey racoon = new String[] { 80082f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey iface, profile.server, "udprsa", privateKey, userCert, 80182f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey caCert, serverCert, "1701", 80282f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey }; 80382f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey break; 80482f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey case VpnProfile.TYPE_IPSEC_XAUTH_PSK: 80582f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey racoon = new String[] { 80682f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey iface, profile.server, "xauthpsk", profile.ipsecIdentifier, 80782f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey profile.ipsecSecret, profile.username, profile.password, "", gateway, 80882f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey }; 80982f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey break; 81082f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey case VpnProfile.TYPE_IPSEC_XAUTH_RSA: 81182f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey racoon = new String[] { 81282f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey iface, profile.server, "xauthrsa", privateKey, userCert, 81382f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey caCert, serverCert, profile.username, profile.password, "", gateway, 81482f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey }; 81582f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey break; 81682f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey case VpnProfile.TYPE_IPSEC_HYBRID_RSA: 81782f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey racoon = new String[] { 81882f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey iface, profile.server, "hybridrsa", 81982f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey caCert, serverCert, profile.username, profile.password, "", gateway, 82082f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey }; 82182f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey break; 82282f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey } 82382f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey 82482f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey // Prepare arguments for mtpd. 82582f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey String[] mtpd = null; 82682f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey switch (profile.type) { 82782f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey case VpnProfile.TYPE_PPTP: 82882f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey mtpd = new String[] { 82982f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey iface, "pptp", profile.server, "1723", 83082f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey "name", profile.username, "password", profile.password, 83182f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey "linkname", "vpn", "refuse-eap", "nodefaultroute", 83282f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400", 83382f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey (profile.mppe ? "+mppe" : "nomppe"), 83482f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey }; 83582f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey break; 83682f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey case VpnProfile.TYPE_L2TP_IPSEC_PSK: 83782f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey case VpnProfile.TYPE_L2TP_IPSEC_RSA: 83882f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey mtpd = new String[] { 83982f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey iface, "l2tp", profile.server, "1701", profile.l2tpSecret, 84082f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey "name", profile.username, "password", profile.password, 84182f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey "linkname", "vpn", "refuse-eap", "nodefaultroute", 84282f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400", 84382f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey }; 84482f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey break; 84582f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey } 846899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey 84782f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey VpnConfig config = new VpnConfig(); 848899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey config.legacy = true; 84982f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey config.user = profile.key; 85082f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey config.interfaze = iface; 85182f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey config.session = profile.name; 8524ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker 8534ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker config.addLegacyRoutes(profile.routes); 85482f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey if (!profile.dnsServers.isEmpty()) { 85582f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey config.dnsServers = Arrays.asList(profile.dnsServers.split(" +")); 85682f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey } 85782f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey if (!profile.searchDomains.isEmpty()) { 85882f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey config.searchDomains = Arrays.asList(profile.searchDomains.split(" +")); 85982f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey } 86082f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey startLegacyVpn(config, racoon, mtpd); 86182f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey } 86282f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey 86382f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) { 86482f8521d386f3109147c477d04e5e90e5c715fa0Jeff Sharkey stopLegacyVpn(); 865899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey 866100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh // Prepare for the new request. This also checks the caller. 867100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh prepare(null, VpnConfig.LEGACY_VPN); 868899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey updateState(DetailedState.CONNECTING, "startLegacyVpn"); 86985a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 8702e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yeh // Start a new LegacyVpnRunner and we are done! 871100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd); 872100155a310fbb5028fc48e359bdfb7c4d3531843Chia-chi Yeh mLegacyVpnRunner.start(); 87385a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 87485a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 875899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey public synchronized void stopLegacyVpn() { 876899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey if (mLegacyVpnRunner != null) { 877899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey mLegacyVpnRunner.exit(); 878899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey mLegacyVpnRunner = null; 879899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey 880899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey synchronized (LegacyVpnRunner.TAG) { 881899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey // wait for old thread to completely finish before spinning up 882899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey // new instance, otherwise state updates can be out of order. 883899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 884899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 885899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 886899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey 88785a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh /** 8882e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yeh * Return the information of the current ongoing legacy VPN. 8892e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yeh */ 8902e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yeh public synchronized LegacyVpnInfo getLegacyVpnInfo() { 891dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh // Check if the caller is authorized. 892dadc857d9de364fded10d4f69eb82bc9cd35d4b7Chia-chi Yeh enforceControlPermission(); 893899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey if (mLegacyVpnRunner == null) return null; 894899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey 895899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey final LegacyVpnInfo info = new LegacyVpnInfo(); 896c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker info.key = mConfig.user; 897899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey info.state = LegacyVpnInfo.stateFromNetworkInfo(mNetworkInfo); 898899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey if (mNetworkInfo.isConnected()) { 899899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey info.intent = mStatusIntent; 900899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 901899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey return info; 9022e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yeh } 9032e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yeh 90469ddab4575ff684c533c995e07ca15fe18543fc0Jeff Sharkey public VpnConfig getLegacyVpnConfig() { 90569ddab4575ff684c533c995e07ca15fe18543fc0Jeff Sharkey if (mLegacyVpnRunner != null) { 906c2865195b66490bd1f9d3df4fe4f5e2a46e2196aChad Brubaker return mConfig; 90769ddab4575ff684c533c995e07ca15fe18543fc0Jeff Sharkey } else { 90869ddab4575ff684c533c995e07ca15fe18543fc0Jeff Sharkey return null; 90969ddab4575ff684c533c995e07ca15fe18543fc0Jeff Sharkey } 91069ddab4575ff684c533c995e07ca15fe18543fc0Jeff Sharkey } 91169ddab4575ff684c533c995e07ca15fe18543fc0Jeff Sharkey 9122e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yeh /** 91385a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh * Bringing up a VPN connection takes time, and that is all this thread 91485a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh * does. Here we have plenty of time. The only thing we need to take 91585a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh * care of is responding to interruptions as soon as possible. Otherwise 91685a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh * requests will be piled up. This can be done in a Handler as a state 91785a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh * machine, but it is much easier to read in the current form. 91885a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh */ 91985a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh private class LegacyVpnRunner extends Thread { 92085a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh private static final String TAG = "LegacyVpnRunner"; 92185a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 9221f7746b39b94be1149228751e45a40ea39603611Chia-chi Yeh private final String[] mDaemons; 92385a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh private final String[][] mArguments; 9245317f034bacaab19af3181da8e9752cbb5b09a08Chia-chi Yeh private final LocalSocket[] mSockets; 92553c04bdd35a85aa65d1a1f18ca2ee34970e2c2d0Robert Greenwalt private final String mOuterInterface; 9261b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt private final AtomicInteger mOuterConnection = 9271b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt new AtomicInteger(ConnectivityManager.TYPE_NONE); 9282e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yeh 92985a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh private long mTimer = -1; 93085a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 9311b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt /** 9321b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt * Watch for the outer connection (passing in the constructor) going away. 9331b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt */ 9341b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 9351b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt @Override 9361b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt public void onReceive(Context context, Intent intent) { 93757666934b4a161cc2fb77eef15d0b894aaf8f173Jeff Sharkey if (!mEnableTeardown) return; 93857666934b4a161cc2fb77eef15d0b894aaf8f173Jeff Sharkey 9391b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) { 9401b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt if (intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, 9411b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt ConnectivityManager.TYPE_NONE) == mOuterConnection.get()) { 9421b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt NetworkInfo info = (NetworkInfo)intent.getExtra( 9431b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt ConnectivityManager.EXTRA_NETWORK_INFO); 9441b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt if (info != null && !info.isConnectedOrConnecting()) { 9451b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt try { 9461b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt mObserver.interfaceStatusChanged(mOuterInterface, false); 9471b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt } catch (RemoteException e) {} 9481b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt } 9491b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt } 9501b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt } 9511b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt } 9521b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt }; 9531b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt 95441d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh public LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd) { 95585a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh super(TAG); 95641d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh mConfig = config; 95741d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh mDaemons = new String[] {"racoon", "mtpd"}; 958899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey // TODO: clear arguments from memory once launched 95941d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh mArguments = new String[][] {racoon, mtpd}; 9605317f034bacaab19af3181da8e9752cbb5b09a08Chia-chi Yeh mSockets = new LocalSocket[mDaemons.length]; 96153c04bdd35a85aa65d1a1f18ca2ee34970e2c2d0Robert Greenwalt 96253c04bdd35a85aa65d1a1f18ca2ee34970e2c2d0Robert Greenwalt // This is the interface which VPN is running on, 96353c04bdd35a85aa65d1a1f18ca2ee34970e2c2d0Robert Greenwalt // mConfig.interfaze will change to point to OUR 96453c04bdd35a85aa65d1a1f18ca2ee34970e2c2d0Robert Greenwalt // internal interface soon. TODO - add inner/outer to mconfig 9651b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt // TODO - we have a race - if the outer iface goes away/disconnects before we hit this 9664ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker // we will leave the VPN up. We should check that it's still there/connected after 9671b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt // registering 96853c04bdd35a85aa65d1a1f18ca2ee34970e2c2d0Robert Greenwalt mOuterInterface = mConfig.interfaze; 9691b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt 9701b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt try { 9711b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt mOuterConnection.set( 9721b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt mConnService.findConnectionTypeForIface(mOuterInterface)); 9731b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt } catch (Exception e) { 9741b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt mOuterConnection.set(ConnectivityManager.TYPE_NONE); 9751b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt } 9761b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt 9771b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt IntentFilter filter = new IntentFilter(); 9781b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); 9791b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt mContext.registerReceiver(mBroadcastReceiver, filter); 98041d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh } 98141d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh 982aa1727fe0cbb902c5f53a3fae601b4e15da0a2f4Chia-chi Yeh public void check(String interfaze) { 98353c04bdd35a85aa65d1a1f18ca2ee34970e2c2d0Robert Greenwalt if (interfaze.equals(mOuterInterface)) { 984aa1727fe0cbb902c5f53a3fae601b4e15da0a2f4Chia-chi Yeh Log.i(TAG, "Legacy VPN is going down with " + interfaze); 985aa1727fe0cbb902c5f53a3fae601b4e15da0a2f4Chia-chi Yeh exit(); 986aa1727fe0cbb902c5f53a3fae601b4e15da0a2f4Chia-chi Yeh } 987aa1727fe0cbb902c5f53a3fae601b4e15da0a2f4Chia-chi Yeh } 988aa1727fe0cbb902c5f53a3fae601b4e15da0a2f4Chia-chi Yeh 98941d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh public void exit() { 9905317f034bacaab19af3181da8e9752cbb5b09a08Chia-chi Yeh // We assume that everything is reset after stopping the daemons. 99197a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh interrupt(); 9925317f034bacaab19af3181da8e9752cbb5b09a08Chia-chi Yeh for (LocalSocket socket : mSockets) { 993065b299df4159602327977dd007cb2cd6b64ab20Jeff Sharkey IoUtils.closeQuietly(socket); 99441d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh } 9956bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen agentDisconnect(); 9961b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt try { 9971b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt mContext.unregisterReceiver(mBroadcastReceiver); 9981b0ca9dace3fb3b84f8a87e539c0179e6093b423Robert Greenwalt } catch (IllegalArgumentException e) {} 9992e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yeh } 10002e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yeh 100185a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh @Override 100285a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh public void run() { 100385a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh // Wait for the previous thread since it has been interrupted. 10042e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yeh Log.v(TAG, "Waiting"); 100585a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh synchronized (TAG) { 10062e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yeh Log.v(TAG, "Executing"); 100785a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh execute(); 1008899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey monitorDaemons(); 100985a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 101085a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 101185a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 101285a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh private void checkpoint(boolean yield) throws InterruptedException { 101385a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh long now = SystemClock.elapsedRealtime(); 101485a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh if (mTimer == -1) { 101585a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh mTimer = now; 101685a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh Thread.sleep(1); 10177ef8611b5f3a893a46c7b9e22bdd8ab252e373ffChia-chi Yeh } else if (now - mTimer <= 60000) { 101885a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh Thread.sleep(yield ? 200 : 1); 101985a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } else { 1020899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey updateState(DetailedState.FAILED, "checkpoint"); 102197a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh throw new IllegalStateException("Time is up"); 102285a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 102385a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 102485a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 102585a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh private void execute() { 102685a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh // Catch all exceptions so we can clean up few things. 1027899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey boolean initFinished = false; 102885a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh try { 102985a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh // Initialize the timer. 103085a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh checkpoint(false); 103185a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 10321f7746b39b94be1149228751e45a40ea39603611Chia-chi Yeh // Wait for the daemons to stop. 10331f7746b39b94be1149228751e45a40ea39603611Chia-chi Yeh for (String daemon : mDaemons) { 1034088f29f55eebc6862a4cb5dddeaefacf24f74d95Jeff Sharkey while (!SystemService.isStopped(daemon)) { 103585a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh checkpoint(true); 103685a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 103785a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 103885a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 103997a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh // Clear the previous state. 104097a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh File state = new File("/data/misc/vpn/state"); 104197a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh state.delete(); 104297a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh if (state.exists()) { 104397a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh throw new IllegalStateException("Cannot delete the state"); 104485a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 1045c1872732922214de80f790e14865e41dd1b98203Chia-chi Yeh new File("/data/misc/vpn/abort").delete(); 1046899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey initFinished = true; 104785a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 1048e9107901ae264de4ff5603d3cfc63a03ca4117d4Chia-chi Yeh // Check if we need to restart any of the daemons. 104985a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh boolean restart = false; 105085a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh for (String[] arguments : mArguments) { 105185a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh restart = restart || (arguments != null); 105285a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 105385a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh if (!restart) { 10546bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen agentDisconnect(); 105585a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh return; 105685a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 1057899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey updateState(DetailedState.CONNECTING, "execute"); 105885a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 10591f7746b39b94be1149228751e45a40ea39603611Chia-chi Yeh // Start the daemon with arguments. 10601f7746b39b94be1149228751e45a40ea39603611Chia-chi Yeh for (int i = 0; i < mDaemons.length; ++i) { 106185a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh String[] arguments = mArguments[i]; 106285a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh if (arguments == null) { 106385a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh continue; 106485a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 106585a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 10661f7746b39b94be1149228751e45a40ea39603611Chia-chi Yeh // Start the daemon. 10671f7746b39b94be1149228751e45a40ea39603611Chia-chi Yeh String daemon = mDaemons[i]; 1068088f29f55eebc6862a4cb5dddeaefacf24f74d95Jeff Sharkey SystemService.start(daemon); 106985a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 10701f7746b39b94be1149228751e45a40ea39603611Chia-chi Yeh // Wait for the daemon to start. 1071088f29f55eebc6862a4cb5dddeaefacf24f74d95Jeff Sharkey while (!SystemService.isRunning(daemon)) { 107285a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh checkpoint(true); 107385a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 107485a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 107585a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh // Create the control socket. 10765317f034bacaab19af3181da8e9752cbb5b09a08Chia-chi Yeh mSockets[i] = new LocalSocket(); 107785a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh LocalSocketAddress address = new LocalSocketAddress( 10781f7746b39b94be1149228751e45a40ea39603611Chia-chi Yeh daemon, LocalSocketAddress.Namespace.RESERVED); 107985a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 108085a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh // Wait for the socket to connect. 108185a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh while (true) { 108285a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh try { 10835317f034bacaab19af3181da8e9752cbb5b09a08Chia-chi Yeh mSockets[i].connect(address); 108485a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh break; 108585a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } catch (Exception e) { 108685a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh // ignore 108785a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 108885a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh checkpoint(true); 108985a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 10905317f034bacaab19af3181da8e9752cbb5b09a08Chia-chi Yeh mSockets[i].setSoTimeout(500); 109185a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 109285a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh // Send over the arguments. 10935317f034bacaab19af3181da8e9752cbb5b09a08Chia-chi Yeh OutputStream out = mSockets[i].getOutputStream(); 109485a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh for (String argument : arguments) { 1095d396a448b2e36e29598c954b64bfddef73f3fae0Elliott Hughes byte[] bytes = argument.getBytes(StandardCharsets.UTF_8); 10965317f034bacaab19af3181da8e9752cbb5b09a08Chia-chi Yeh if (bytes.length >= 0xFFFF) { 109797a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh throw new IllegalArgumentException("Argument is too large"); 109885a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 10991f7746b39b94be1149228751e45a40ea39603611Chia-chi Yeh out.write(bytes.length >> 8); 11001f7746b39b94be1149228751e45a40ea39603611Chia-chi Yeh out.write(bytes.length); 11011f7746b39b94be1149228751e45a40ea39603611Chia-chi Yeh out.write(bytes); 110285a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh checkpoint(false); 110385a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 11045317f034bacaab19af3181da8e9752cbb5b09a08Chia-chi Yeh out.write(0xFF); 11055317f034bacaab19af3181da8e9752cbb5b09a08Chia-chi Yeh out.write(0xFF); 11061f7746b39b94be1149228751e45a40ea39603611Chia-chi Yeh out.flush(); 110797a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh 110897a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh // Wait for End-of-File. 11095317f034bacaab19af3181da8e9752cbb5b09a08Chia-chi Yeh InputStream in = mSockets[i].getInputStream(); 111097a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh while (true) { 111197a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh try { 111297a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh if (in.read() == -1) { 111397a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh break; 111497a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh } 111597a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh } catch (Exception e) { 111697a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh // ignore 111797a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh } 111897a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh checkpoint(true); 111997a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh } 112085a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 112185a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 112297a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh // Wait for the daemons to create the new state. 112397a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh while (!state.exists()) { 11241f7746b39b94be1149228751e45a40ea39603611Chia-chi Yeh // Check if a running daemon is dead. 11251f7746b39b94be1149228751e45a40ea39603611Chia-chi Yeh for (int i = 0; i < mDaemons.length; ++i) { 11261f7746b39b94be1149228751e45a40ea39603611Chia-chi Yeh String daemon = mDaemons[i]; 1127088f29f55eebc6862a4cb5dddeaefacf24f74d95Jeff Sharkey if (mArguments[i] != null && !SystemService.isRunning(daemon)) { 11282e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yeh throw new IllegalStateException(daemon + " is dead"); 112985a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 113085a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 113185a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh checkpoint(true); 113285a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 113385a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 113497a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh // Now we are connected. Read and parse the new state. 1135c1bac3a6e240c1c9a14a7b515f585977fb908930Chia-chi Yeh String[] parameters = FileUtils.readTextFile(state, 0, null).split("\n", -1); 113697a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh if (parameters.length != 6) { 113797a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh throw new IllegalStateException("Cannot parse the state"); 113897a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh } 113997a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh 114097a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh // Set the interface and the addresses in the config. 114197a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh mConfig.interfaze = parameters[0].trim(); 114285a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh 11434ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker mConfig.addLegacyAddresses(parameters[1]); 114497a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh // Set the routes if they are not set in the config. 114597a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh if (mConfig.routes == null || mConfig.routes.isEmpty()) { 11464ca19e8377f33e8a80684fb4ee67f5a4bdc9ea76Chad Brubaker mConfig.addLegacyRoutes(parameters[2]); 114797a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh } 114897a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh 114997a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh // Set the DNS servers if they are not set in the config. 115041d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) { 115197a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh String dnsServers = parameters[3].trim(); 115241d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh if (!dnsServers.isEmpty()) { 115341d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh mConfig.dnsServers = Arrays.asList(dnsServers.split(" ")); 115441d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh } 115541d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh } 115641d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh 115797a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh // Set the search domains if they are not set in the config. 115897a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) { 115997a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh String searchDomains = parameters[4].trim(); 116097a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh if (!searchDomains.isEmpty()) { 116197a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh mConfig.searchDomains = Arrays.asList(searchDomains.split(" ")); 116297a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh } 116397a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh } 116497a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh 116597a61565ea95472e65899070e64853f8c147bb11Chia-chi Yeh // Here is the last step and it must be done synchronously. 116641d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh synchronized (Vpn.this) { 11672b862e5e75ad419f17a4cea185b9349e0da70e7bVinit Deshapnde // Set the start time 11682b862e5e75ad419f17a4cea185b9349e0da70e7bVinit Deshapnde mConfig.startTime = SystemClock.elapsedRealtime(); 11692b862e5e75ad419f17a4cea185b9349e0da70e7bVinit Deshapnde 117041d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh // Check if the thread is interrupted while we are waiting. 117141d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh checkpoint(false); 117241d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh 1173e9107901ae264de4ff5603d3cfc63a03ca4117d4Chia-chi Yeh // Check if the interface is gone while we are waiting. 1174c2b8aa0b4c822b0e307f62131650f4a6ee89bb66Chia-chi Yeh if (jniCheck(mConfig.interfaze) == 0) { 117534e7813e962de99df9813014678ef5901227c5f1Chia-chi Yeh throw new IllegalStateException(mConfig.interfaze + " is gone"); 117641d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh } 1177e9107901ae264de4ff5603d3cfc63a03ca4117d4Chia-chi Yeh 1178e9107901ae264de4ff5603d3cfc63a03ca4117d4Chia-chi Yeh // Now INetworkManagementEventObserver is watching our back. 1179c2b8aa0b4c822b0e307f62131650f4a6ee89bb66Chia-chi Yeh mInterface = mConfig.interfaze; 11806bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen mVpnUsers = new ArrayList<UidRange>(); 11816bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen 11826bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen agentConnect(); 11832e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yeh 11842e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yeh Log.i(TAG, "Connected!"); 118541d1685a22ba8038517d6fdb57006023e03f12e1Chia-chi Yeh } 118685a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } catch (Exception e) { 11872e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yeh Log.i(TAG, "Aborting", e); 1188e9107901ae264de4ff5603d3cfc63a03ca4117d4Chia-chi Yeh exit(); 11892e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yeh } finally { 11905317f034bacaab19af3181da8e9752cbb5b09a08Chia-chi Yeh // Kill the daemons if they fail to stop. 1191899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey if (!initFinished) { 11925317f034bacaab19af3181da8e9752cbb5b09a08Chia-chi Yeh for (String daemon : mDaemons) { 1193088f29f55eebc6862a4cb5dddeaefacf24f74d95Jeff Sharkey SystemService.stop(daemon); 11945317f034bacaab19af3181da8e9752cbb5b09a08Chia-chi Yeh } 11955317f034bacaab19af3181da8e9752cbb5b09a08Chia-chi Yeh } 11965317f034bacaab19af3181da8e9752cbb5b09a08Chia-chi Yeh 11972e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yeh // Do not leave an unstable state. 1198899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey if (!initFinished || mNetworkInfo.getDetailedState() == DetailedState.CONNECTING) { 11996bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen agentDisconnect(); 12002e46764a707bd14cad22bc179669eeecb2d7c647Chia-chi Yeh } 120185a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 120285a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 1203899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey 1204899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey /** 1205899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey * Monitor the daemons we started, moving to disconnected state if the 1206899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey * underlying services fail. 1207899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey */ 1208899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey private void monitorDaemons() { 1209899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey if (!mNetworkInfo.isConnected()) { 1210899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey return; 1211899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 1212899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey 1213899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey try { 1214899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey while (true) { 1215899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey Thread.sleep(2000); 1216899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey for (int i = 0; i < mDaemons.length; i++) { 1217899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey if (mArguments[i] != null && SystemService.isStopped(mDaemons[i])) { 1218899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey return; 1219899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 1220899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 1221899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 1222899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } catch (InterruptedException e) { 1223899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey Log.d(TAG, "interrupted during monitorDaemons(); stopping services"); 1224899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } finally { 1225899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey for (String daemon : mDaemons) { 1226899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey SystemService.stop(daemon); 1227899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 1228899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey 12296bc2c2c34f2b23eae79ad733c97a691734055c4fPaul Jensen agentDisconnect(); 1230899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 1231899223b97c9b0ae56a8211a46600914c0ecfd854Jeff Sharkey } 123285a7ce02c8b53e1efaa5a3a32f0a5ec7b549f758Chia-chi Yeh } 1233ff3bdca31f4cf2bd607519b276dd175763aa1784Chia-chi Yeh} 1234