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