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