1 /*
2  * Copyright (C) 2014 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.net.hostside;
18 
19 import android.content.Intent;
20 import android.content.pm.PackageManager.NameNotFoundException;
21 import android.net.IpPrefix;
22 import android.net.Network;
23 import android.net.NetworkUtils;
24 import android.net.ProxyInfo;
25 import android.net.VpnService;
26 import android.os.ParcelFileDescriptor;
27 import android.text.TextUtils;
28 import android.util.Log;
29 import android.util.Pair;
30 
31 import com.android.modules.utils.build.SdkLevel;
32 import com.android.networkstack.apishim.VpnServiceBuilderShimImpl;
33 import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
34 import com.android.networkstack.apishim.common.VpnServiceBuilderShim;
35 import com.android.testutils.PacketReflector;
36 
37 import java.io.IOException;
38 import java.net.InetAddress;
39 import java.util.ArrayList;
40 import java.util.function.BiConsumer;
41 import java.util.function.Consumer;
42 
43 public class MyVpnService extends VpnService {
44 
45     private static String TAG = "MyVpnService";
46     private static int MTU = 1799;
47 
48     public static final String ACTION_ESTABLISHED = "com.android.cts.net.hostside.ESTABNLISHED";
49     public static final String EXTRA_ALWAYS_ON = "is-always-on";
50     public static final String EXTRA_LOCKDOWN_ENABLED = "is-lockdown-enabled";
51     public static final String CMD_CONNECT = "connect";
52     public static final String CMD_DISCONNECT = "disconnect";
53     public static final String CMD_UPDATE_UNDERLYING_NETWORKS = "update_underlying_networks";
54 
55     private ParcelFileDescriptor mFd = null;
56     private PacketReflector mPacketReflector = null;
57 
58     @Override
onStartCommand(Intent intent, int flags, int startId)59     public int onStartCommand(Intent intent, int flags, int startId) {
60         String packageName = getPackageName();
61         String cmd = intent.getStringExtra(packageName + ".cmd");
62         if (CMD_DISCONNECT.equals(cmd)) {
63             stop();
64         } else if (CMD_CONNECT.equals(cmd)) {
65             start(packageName, intent);
66         } else if (CMD_UPDATE_UNDERLYING_NETWORKS.equals(cmd)) {
67             updateUnderlyingNetworks(packageName, intent);
68         }
69 
70         return START_NOT_STICKY;
71     }
72 
updateUnderlyingNetworks(String packageName, Intent intent)73     private void updateUnderlyingNetworks(String packageName, Intent intent) {
74         final ArrayList<Network> underlyingNetworks =
75                 intent.getParcelableArrayListExtra(packageName + ".underlyingNetworks");
76         setUnderlyingNetworks(
77                 (underlyingNetworks != null) ? underlyingNetworks.toArray(new Network[0]) : null);
78     }
79 
parseIpAndMaskListArgument(String packageName, Intent intent, String argName, BiConsumer<InetAddress, Integer> consumer)80     private String parseIpAndMaskListArgument(String packageName, Intent intent, String argName,
81             BiConsumer<InetAddress, Integer> consumer) {
82         final String addresses = intent.getStringExtra(packageName + "." + argName);
83 
84         if (TextUtils.isEmpty(addresses)) {
85             return null;
86         }
87 
88         final String[] addressesArray = addresses.split(",");
89         for (String address : addressesArray) {
90             final Pair<InetAddress, Integer> ipAndMask = NetworkUtils.parseIpAndMask(address);
91             consumer.accept(ipAndMask.first, ipAndMask.second);
92         }
93 
94         return addresses;
95     }
96 
parseIpPrefixListArgument(String packageName, Intent intent, String argName, Consumer<IpPrefix> consumer)97     private String parseIpPrefixListArgument(String packageName, Intent intent, String argName,
98             Consumer<IpPrefix> consumer) {
99         return parseIpAndMaskListArgument(packageName, intent, argName,
100                 (inetAddress, prefixLength) -> consumer.accept(
101                         new IpPrefix(inetAddress, prefixLength)));
102     }
103 
start(String packageName, Intent intent)104     private void start(String packageName, Intent intent) {
105         Builder builder = new Builder();
106         VpnServiceBuilderShim vpnServiceBuilderShim = VpnServiceBuilderShimImpl.newInstance();
107 
108         final String addresses = parseIpAndMaskListArgument(packageName, intent, "addresses",
109                 builder::addAddress);
110 
111         String addedRoutes;
112         if (SdkLevel.isAtLeastT() && intent.getBooleanExtra(packageName + ".addRoutesByIpPrefix",
113                 false)) {
114             addedRoutes = parseIpPrefixListArgument(packageName, intent, "routes", (prefix) -> {
115                 try {
116                     vpnServiceBuilderShim.addRoute(builder, prefix);
117                 } catch (UnsupportedApiLevelException e) {
118                     throw new RuntimeException(e);
119                 }
120             });
121         } else {
122             addedRoutes = parseIpAndMaskListArgument(packageName, intent, "routes",
123                     builder::addRoute);
124         }
125 
126         String excludedRoutes = null;
127         if (SdkLevel.isAtLeastT()) {
128             excludedRoutes = parseIpPrefixListArgument(packageName, intent, "excludedRoutes",
129                     (prefix) -> {
130                         try {
131                             vpnServiceBuilderShim.excludeRoute(builder, prefix);
132                         } catch (UnsupportedApiLevelException e) {
133                             throw new RuntimeException(e);
134                         }
135                     });
136         }
137 
138         String allowed = intent.getStringExtra(packageName + ".allowedapplications");
139         if (allowed != null) {
140             String[] packageArray = allowed.split(",");
141             for (int i = 0; i < packageArray.length; i++) {
142                 String allowedPackage = packageArray[i];
143                 if (!TextUtils.isEmpty(allowedPackage)) {
144                     try {
145                         builder.addAllowedApplication(allowedPackage);
146                     } catch(NameNotFoundException e) {
147                         continue;
148                     }
149                 }
150             }
151         }
152 
153         String disallowed = intent.getStringExtra(packageName + ".disallowedapplications");
154         if (disallowed != null) {
155             String[] packageArray = disallowed.split(",");
156             for (int i = 0; i < packageArray.length; i++) {
157                 String disallowedPackage = packageArray[i];
158                 if (!TextUtils.isEmpty(disallowedPackage)) {
159                     try {
160                         builder.addDisallowedApplication(disallowedPackage);
161                     } catch(NameNotFoundException e) {
162                         continue;
163                     }
164                 }
165             }
166         }
167 
168         ArrayList<Network> underlyingNetworks =
169                 intent.getParcelableArrayListExtra(packageName + ".underlyingNetworks");
170         if (underlyingNetworks == null) {
171             // VPN tracks default network
172             builder.setUnderlyingNetworks(null);
173         } else {
174             builder.setUnderlyingNetworks(underlyingNetworks.toArray(new Network[0]));
175         }
176 
177         boolean isAlwaysMetered = intent.getBooleanExtra(packageName + ".isAlwaysMetered", false);
178         builder.setMetered(isAlwaysMetered);
179 
180         ProxyInfo vpnProxy = intent.getParcelableExtra(packageName + ".httpProxy");
181         builder.setHttpProxy(vpnProxy);
182         builder.setMtu(MTU);
183         builder.setBlocking(true);
184         builder.setSession("MyVpnService");
185 
186         Log.i(TAG, "Establishing VPN,"
187                 + " addresses=" + addresses
188                 + " addedRoutes=" + addedRoutes
189                 + " excludedRoutes=" + excludedRoutes
190                 + " allowedApplications=" + allowed
191                 + " disallowedApplications=" + disallowed);
192 
193         mFd = builder.establish();
194         Log.i(TAG, "Established, fd=" + (mFd == null ? "null" : mFd.getFd()));
195 
196         broadcastEstablished();
197 
198         mPacketReflector = new PacketReflector(mFd.getFileDescriptor(), MTU);
199         mPacketReflector.start();
200     }
201 
broadcastEstablished()202     private void broadcastEstablished() {
203         final Intent bcIntent = new Intent(ACTION_ESTABLISHED);
204         bcIntent.putExtra(EXTRA_ALWAYS_ON, isAlwaysOn());
205         bcIntent.putExtra(EXTRA_LOCKDOWN_ENABLED, isLockdownEnabled());
206         sendBroadcast(bcIntent);
207     }
208 
stop()209     private void stop() {
210         if (mPacketReflector != null) {
211             mPacketReflector.interrupt();
212             mPacketReflector = null;
213         }
214         try {
215             if (mFd != null) {
216                 Log.i(TAG, "Closing filedescriptor");
217                 mFd.close();
218             }
219         } catch(IOException e) {
220         } finally {
221             mFd = null;
222         }
223     }
224 
225     @Override
onDestroy()226     public void onDestroy() {
227         stop();
228         super.onDestroy();
229     }
230 }
231