• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.cts.vpnfirewall;
18 
19 import android.R;
20 import android.app.Notification;
21 import android.app.NotificationChannel;
22 import android.app.NotificationManager;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.pm.PackageManager.NameNotFoundException;
26 import android.net.ConnectivityManager;
27 import android.net.Network;
28 import android.net.NetworkCapabilities;
29 import android.net.NetworkRequest;
30 import android.net.VpnService;
31 import android.os.Bundle;
32 import android.os.ParcelFileDescriptor;
33 import android.os.UserManager;
34 import android.text.TextUtils;
35 import android.util.Log;
36 import android.os.SystemProperties;
37 
38 import java.io.IOException;
39 import java.net.InetAddress;
40 import java.net.UnknownHostException;
41 
42 public class ReflectorVpnService extends VpnService {
43     public static final String ACTION_STOP_SERVICE = "com.android.cts.vpnfirewall.STOP_SERVICE";
44 
45     private static final String TAG = "ReflectorVpnService";
46     private static final String DEVICE_AND_PROFILE_OWNER_PACKAGE =
47         "com.android.cts.deviceandprofileowner";
48     private static final String ACTION_VPN_IS_UP = "com.android.cts.vpnfirewall.VPN_IS_UP";
49     private static final String ACTION_VPN_ON_START = "com.android.cts.vpnfirewall.VPN_ON_START";
50     private static final int NOTIFICATION_ID = 1;
51     private static final String NOTIFICATION_CHANNEL_ID = TAG;
52     private static final int MTU = 1799;
53 
54     private ParcelFileDescriptor mFd = null;
55     private PingReflector mPingReflector = null;
56     private ConnectivityManager mConnectivityManager = null;
57     private ConnectivityManager.NetworkCallback mNetworkCallback = null;
58 
59     private static final String RESTRICTION_ADDRESSES = "vpn.addresses";
60     private static final String RESTRICTION_ROUTES = "vpn.routes";
61     private static final String RESTRICTION_ALLOWED = "vpn.allowed";
62     private static final String RESTRICTION_DISALLOWED = "vpn.disallowed";
63     /** Service won't create the tunnel, to test lockdown behavior in case of VPN failure. */
64     private static final String RESTRICTION_DONT_ESTABLISH = "vpn.dont_establish";
65     private static final String EXTRA_ALWAYS_ON = "always-on";
66     private static final String EXTRA_LOCKDOWN = "lockdown";
67 
68     @Override
onStartCommand(Intent intent, int flags, int startId)69     public int onStartCommand(Intent intent, int flags, int startId) {
70         if (ACTION_STOP_SERVICE.equals(intent.getAction())) {
71             stop();
72             stopSelf();
73         } else {
74             // Normal service start
75             // Put ourself in the foreground to stop the system killing us while we wait for orders from
76             // the hostside test.
77             NotificationManager notificationManager = getSystemService(NotificationManager.class);
78             notificationManager.createNotificationChannel(new NotificationChannel(
79                     NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
80                     NotificationManager.IMPORTANCE_DEFAULT));
81             startForeground(NOTIFICATION_ID, new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
82                     .setSmallIcon(R.drawable.ic_dialog_alert)
83                     .build());
84             start();
85         }
86         return START_NOT_STICKY;
87     }
88 
89     @Override
onCreate()90     public void onCreate() {
91         mConnectivityManager = getSystemService(ConnectivityManager.class);
92     }
93 
94     @Override
onDestroy()95     public void onDestroy() {
96         stop();
97         NotificationManager notificationManager = getSystemService(NotificationManager.class);
98         notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
99         ensureNetworkCallbackUnregistered();
100         super.onDestroy();
101     }
102 
ensureNetworkCallbackUnregistered()103     private void ensureNetworkCallbackUnregistered() {
104         if (null == mConnectivityManager || null == mNetworkCallback) return;
105         mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
106         mNetworkCallback = null;
107     }
108 
start()109     private void start() {
110         final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
111         final Bundle restrictions = um.getApplicationRestrictions(getPackageName());
112 
113         final Intent intent = new Intent(ACTION_VPN_ON_START);
114         intent.setPackage(DEVICE_AND_PROFILE_OWNER_PACKAGE);
115         sendBroadcast(intent);
116 
117         if (restrictions.getBoolean(RESTRICTION_DONT_ESTABLISH)) {
118             stopSelf();
119             return;
120         }
121 
122         VpnService.prepare(this);
123 
124         ensureNetworkCallbackUnregistered();
125         final NetworkRequest request = new NetworkRequest.Builder()
126             .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
127             .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
128             .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
129             .build();
130         mNetworkCallback = new ConnectivityManager.NetworkCallback() {
131                 @Override
132                 public void onAvailable(final Network net) {
133                     final Intent intent = new Intent(ACTION_VPN_IS_UP);
134                     intent.setPackage(DEVICE_AND_PROFILE_OWNER_PACKAGE);
135                     intent.putExtra(EXTRA_ALWAYS_ON, isAlwaysOn());
136                     intent.putExtra(EXTRA_LOCKDOWN, isLockdownEnabled());
137                     sendBroadcast(intent);
138                     ensureNetworkCallbackUnregistered();
139                 }
140             };
141         mConnectivityManager.registerNetworkCallback(request, mNetworkCallback);
142 
143         Builder builder = new Builder();
144 
145 
146         String[] addressArray = restrictions.getStringArray(RESTRICTION_ADDRESSES);
147         if (addressArray == null) {
148             // Addresses for IPv4/IPv6 documentation purposes according to rfc5737/rfc3849.
149             addressArray = new String[] {"192.0.2.3/32", "2001:db8:1:2::/128"};
150         };
151         for (int i = 0; i < addressArray.length; i++) {
152             String[] prefixAndMask = addressArray[i].split("/");
153             try {
154                 InetAddress address = InetAddress.getByName(prefixAndMask[0]);
155                 int prefixLength = Integer.parseInt(prefixAndMask[1]);
156                 builder.addAddress(address, prefixLength);
157             } catch (NumberFormatException | UnknownHostException e) {
158                 Log.w(TAG, "Ill-formed address: " + addressArray[i]);
159                 continue;
160             }
161         }
162 
163         String[] routeArray = restrictions.getStringArray(RESTRICTION_ROUTES);
164         if (routeArray == null) {
165             routeArray = new String[] {"0.0.0.0/0", "::/0"};
166         }
167         for (int i = 0; i < routeArray.length; i++) {
168             String[] prefixAndMask = routeArray[i].split("/");
169             try {
170                 InetAddress address = InetAddress.getByName(prefixAndMask[0]);
171                 int prefixLength = Integer.parseInt(prefixAndMask[1]);
172                 builder.addRoute(address, prefixLength);
173             } catch (NumberFormatException | UnknownHostException e) {
174                 Log.w(TAG, "Ill-formed route: " + routeArray[i]);
175                 continue;
176             }
177         }
178 
179         String[] allowedArray = restrictions.getStringArray(RESTRICTION_ALLOWED);
180         if (allowedArray != null) {
181             for (int i = 0; i < allowedArray.length; i++) {
182                 String allowedPackage = allowedArray[i];
183                 if (!TextUtils.isEmpty(allowedPackage)) {
184                     try {
185                         builder.addAllowedApplication(allowedPackage);
186                     } catch(NameNotFoundException e) {
187                         Log.w(TAG, "Allowed package not found: " + allowedPackage);
188                         continue;
189                     }
190                 }
191             }
192         }
193 
194         String[] disallowedArray = restrictions.getStringArray(RESTRICTION_DISALLOWED);
195         if (disallowedArray != null) {
196             for (int i = 0; i < disallowedArray.length; i++) {
197                 String disallowedPackage = disallowedArray[i];
198                 if (!TextUtils.isEmpty(disallowedPackage)) {
199                     try {
200                         builder.addDisallowedApplication(disallowedPackage);
201                     } catch(NameNotFoundException e) {
202                         Log.w(TAG, "Disallowed package not found: " + disallowedPackage);
203                         continue;
204                     }
205                 }
206             }
207         }
208 
209         if (allowedArray == null &&
210             (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1
211             || SystemProperties.getInt("service.adb.tcp.port", -1) > -1)) {
212             try {
213                 // If adb TCP port opened the test may be running by adb over network.
214                 // Add com.android.shell application into disallowed list to exclude adb socket
215                 // for VPN tests.
216                 builder.addDisallowedApplication("com.android.shell");
217             } catch(NameNotFoundException e) {
218                 Log.w(TAG, "com.android.shell not found");
219             }
220         }
221 
222         builder.setMtu(MTU);
223         builder.setBlocking(true);
224         builder.setSession(TAG);
225 
226         mFd = builder.establish();
227         if (mFd == null) {
228             Log.e(TAG, "Unable to establish file descriptor for VPN connection");
229             return;
230         }
231         Log.i(TAG, "Established, fd=" + mFd.getFd());
232 
233         mPingReflector = new PingReflector(mFd.getFileDescriptor(), MTU);
234         mPingReflector.start();
235     }
236 
stop()237     private void stop() {
238         if (mPingReflector != null) {
239             mPingReflector.interrupt();
240             mPingReflector = null;
241         }
242         try {
243             if (mFd != null) {
244                 Log.i(TAG, "Closing filedescriptor");
245                 mFd.close();
246             }
247         } catch(IOException e) {
248             Log.w(TAG, "Closing filedescriptor failed", e);
249         } finally {
250             mFd = null;
251         }
252     }
253 }
254