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