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     private static final String TAG = "ReflectorVpnService";
44     private static final String DEVICE_AND_PROFILE_OWNER_PACKAGE =
45         "com.android.cts.deviceandprofileowner";
46     private static final String ACTION_VPN_IS_UP = "com.android.cts.vpnfirewall.VPN_IS_UP";
47     private static final String ACTION_VPN_ON_START = "com.android.cts.vpnfirewall.VPN_ON_START";
48     private static final int NOTIFICATION_ID = 1;
49     private static final String NOTIFICATION_CHANNEL_ID = TAG;
50     private static final int MTU = 1799;
51 
52     private ParcelFileDescriptor mFd = null;
53     private PingReflector mPingReflector = null;
54     private ConnectivityManager mConnectivityManager = null;
55     private ConnectivityManager.NetworkCallback mNetworkCallback = null;
56 
57     private static final String RESTRICTION_ADDRESSES = "vpn.addresses";
58     private static final String RESTRICTION_ROUTES = "vpn.routes";
59     private static final String RESTRICTION_ALLOWED = "vpn.allowed";
60     private static final String RESTRICTION_DISALLOWED = "vpn.disallowed";
61     /** Service won't create the tunnel, to test lockdown behavior in case of VPN failure. */
62     private static final String RESTRICTION_DONT_ESTABLISH = "vpn.dont_establish";
63     private static final String EXTRA_ALWAYS_ON = "always-on";
64     private static final String EXTRA_LOCKDOWN = "lockdown";
65 
66     @Override
onStartCommand(Intent intent, int flags, int startId)67     public int onStartCommand(Intent intent, int flags, int startId) {
68         // Put ourself in the foreground to stop the system killing us while we wait for orders from
69         // the hostside test.
70         NotificationManager notificationManager = getSystemService(NotificationManager.class);
71         notificationManager.createNotificationChannel(new NotificationChannel(
72                 NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
73                 NotificationManager.IMPORTANCE_DEFAULT));
74         startForeground(NOTIFICATION_ID, new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
75                 .setSmallIcon(R.drawable.ic_dialog_alert)
76                 .build());
77         start();
78         return START_NOT_STICKY;
79     }
80 
81     @Override
onCreate()82     public void onCreate() {
83         mConnectivityManager = getSystemService(ConnectivityManager.class);
84     }
85 
86     @Override
onDestroy()87     public void onDestroy() {
88         stop();
89         NotificationManager notificationManager = getSystemService(NotificationManager.class);
90         notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
91         ensureNetworkCallbackUnregistered();
92         super.onDestroy();
93     }
94 
ensureNetworkCallbackUnregistered()95     private void ensureNetworkCallbackUnregistered() {
96         if (null == mConnectivityManager || null == mNetworkCallback) return;
97         mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
98         mNetworkCallback = null;
99     }
100 
start()101     private void start() {
102         final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
103         final Bundle restrictions = um.getApplicationRestrictions(getPackageName());
104 
105         final Intent intent = new Intent(ACTION_VPN_ON_START);
106         intent.setPackage(DEVICE_AND_PROFILE_OWNER_PACKAGE);
107         sendBroadcast(intent);
108 
109         if (restrictions.getBoolean(RESTRICTION_DONT_ESTABLISH)) {
110             stopSelf();
111             return;
112         }
113 
114         VpnService.prepare(this);
115 
116         ensureNetworkCallbackUnregistered();
117         final NetworkRequest request = new NetworkRequest.Builder()
118             .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
119             .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
120             .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
121             .build();
122         mNetworkCallback = new ConnectivityManager.NetworkCallback() {
123                 @Override
124                 public void onAvailable(final Network net) {
125                     final Intent intent = new Intent(ACTION_VPN_IS_UP);
126                     intent.setPackage(DEVICE_AND_PROFILE_OWNER_PACKAGE);
127                     intent.putExtra(EXTRA_ALWAYS_ON, isAlwaysOn());
128                     intent.putExtra(EXTRA_LOCKDOWN, isLockdownEnabled());
129                     sendBroadcast(intent);
130                     ensureNetworkCallbackUnregistered();
131                 }
132             };
133         mConnectivityManager.registerNetworkCallback(request, mNetworkCallback);
134 
135         Builder builder = new Builder();
136 
137 
138         String[] addressArray = restrictions.getStringArray(RESTRICTION_ADDRESSES);
139         if (addressArray == null) {
140             // Addresses for IPv4/IPv6 documentation purposes according to rfc5737/rfc3849.
141             addressArray = new String[] {"192.0.2.3/32", "2001:db8:1:2::/128"};
142         };
143         for (int i = 0; i < addressArray.length; i++) {
144             String[] prefixAndMask = addressArray[i].split("/");
145             try {
146                 InetAddress address = InetAddress.getByName(prefixAndMask[0]);
147                 int prefixLength = Integer.parseInt(prefixAndMask[1]);
148                 builder.addAddress(address, prefixLength);
149             } catch (NumberFormatException | UnknownHostException e) {
150                 Log.w(TAG, "Ill-formed address: " + addressArray[i]);
151                 continue;
152             }
153         }
154 
155         String[] routeArray = restrictions.getStringArray(RESTRICTION_ROUTES);
156         if (routeArray == null) {
157             routeArray = new String[] {"0.0.0.0/0", "::/0"};
158         }
159         for (int i = 0; i < routeArray.length; i++) {
160             String[] prefixAndMask = routeArray[i].split("/");
161             try {
162                 InetAddress address = InetAddress.getByName(prefixAndMask[0]);
163                 int prefixLength = Integer.parseInt(prefixAndMask[1]);
164                 builder.addRoute(address, prefixLength);
165             } catch (NumberFormatException | UnknownHostException e) {
166                 Log.w(TAG, "Ill-formed route: " + routeArray[i]);
167                 continue;
168             }
169         }
170 
171         String[] allowedArray = restrictions.getStringArray(RESTRICTION_ALLOWED);
172         if (allowedArray != null) {
173             for (int i = 0; i < allowedArray.length; i++) {
174                 String allowedPackage = allowedArray[i];
175                 if (!TextUtils.isEmpty(allowedPackage)) {
176                     try {
177                         builder.addAllowedApplication(allowedPackage);
178                     } catch(NameNotFoundException e) {
179                         Log.w(TAG, "Allowed package not found: " + allowedPackage);
180                         continue;
181                     }
182                 }
183             }
184         }
185 
186         String[] disallowedArray = restrictions.getStringArray(RESTRICTION_DISALLOWED);
187         if (disallowedArray != null) {
188             for (int i = 0; i < disallowedArray.length; i++) {
189                 String disallowedPackage = disallowedArray[i];
190                 if (!TextUtils.isEmpty(disallowedPackage)) {
191                     try {
192                         builder.addDisallowedApplication(disallowedPackage);
193                     } catch(NameNotFoundException e) {
194                         Log.w(TAG, "Disallowed package not found: " + disallowedPackage);
195                         continue;
196                     }
197                 }
198             }
199         }
200 
201         if (allowedArray == null &&
202             (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1
203             || SystemProperties.getInt("service.adb.tcp.port", -1) > -1)) {
204             try {
205                 // If adb TCP port opened the test may be running by adb over network.
206                 // Add com.android.shell application into disallowed list to exclude adb socket
207                 // for VPN tests.
208                 builder.addDisallowedApplication("com.android.shell");
209             } catch(NameNotFoundException e) {
210                 Log.w(TAG, "com.android.shell not found");
211             }
212         }
213 
214         builder.setMtu(MTU);
215         builder.setBlocking(true);
216         builder.setSession(TAG);
217 
218         mFd = builder.establish();
219         if (mFd == null) {
220             Log.e(TAG, "Unable to establish file descriptor for VPN connection");
221             return;
222         }
223         Log.i(TAG, "Established, fd=" + mFd.getFd());
224 
225         mPingReflector = new PingReflector(mFd.getFileDescriptor(), MTU);
226         mPingReflector.start();
227     }
228 
stop()229     private void stop() {
230         if (mPingReflector != null) {
231             mPingReflector.interrupt();
232             mPingReflector = null;
233         }
234         try {
235             if (mFd != null) {
236                 Log.i(TAG, "Closing filedescriptor");
237                 mFd.close();
238             }
239         } catch(IOException e) {
240             Log.w(TAG, "Closing filedescriptor failed", e);
241         } finally {
242             mFd = null;
243         }
244     }
245 }
246