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