1 /* 2 * Copyright (C) 2021 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.net.module.util; 18 19 import static android.net.INetd.IF_STATE_DOWN; 20 import static android.net.INetd.IF_STATE_UP; 21 import static android.net.RouteInfo.RTN_THROW; 22 import static android.net.RouteInfo.RTN_UNICAST; 23 import static android.net.RouteInfo.RTN_UNREACHABLE; 24 import static android.system.OsConstants.EBUSY; 25 26 import android.annotation.SuppressLint; 27 import android.net.INetd; 28 import android.net.InterfaceConfigurationParcel; 29 import android.net.IpPrefix; 30 import android.net.RouteInfo; 31 import android.net.RouteInfoParcel; 32 import android.net.TetherConfigParcel; 33 import android.os.RemoteException; 34 import android.os.ServiceSpecificException; 35 import android.os.SystemClock; 36 import android.util.Log; 37 38 import androidx.annotation.NonNull; 39 import androidx.annotation.VisibleForTesting; 40 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.HashSet; 44 import java.util.List; 45 import java.util.Set; 46 47 /** 48 * Collection of utilities for netd. 49 */ 50 public class NetdUtils { 51 private static final String TAG = NetdUtils.class.getSimpleName(); 52 53 /** Used to modify the specified route. */ 54 public enum ModifyOperation { 55 ADD, 56 REMOVE, 57 } 58 59 /** 60 * Get InterfaceConfigurationParcel from netd. 61 */ getInterfaceConfigParcel(@onNull INetd netd, @NonNull String iface)62 public static InterfaceConfigurationParcel getInterfaceConfigParcel(@NonNull INetd netd, 63 @NonNull String iface) { 64 try { 65 return netd.interfaceGetCfg(iface); 66 } catch (RemoteException | ServiceSpecificException e) { 67 throw new IllegalStateException(e); 68 } 69 } 70 validateFlag(String flag)71 private static void validateFlag(String flag) { 72 if (flag.indexOf(' ') >= 0) { 73 throw new IllegalArgumentException("flag contains space: " + flag); 74 } 75 } 76 77 /** 78 * Check whether the InterfaceConfigurationParcel contains the target flag or not. 79 * 80 * @param config The InterfaceConfigurationParcel instance. 81 * @param flag Target flag string to be checked. 82 */ hasFlag(@onNull final InterfaceConfigurationParcel config, @NonNull final String flag)83 public static boolean hasFlag(@NonNull final InterfaceConfigurationParcel config, 84 @NonNull final String flag) { 85 validateFlag(flag); 86 final Set<String> flagList = new HashSet<String>(Arrays.asList(config.flags)); 87 return flagList.contains(flag); 88 } 89 90 @VisibleForTesting removeAndAddFlags(@onNull String[] flags, @NonNull String remove, @NonNull String add)91 protected static String[] removeAndAddFlags(@NonNull String[] flags, @NonNull String remove, 92 @NonNull String add) { 93 final ArrayList<String> result = new ArrayList<>(); 94 try { 95 // Validate the add flag first, so that the for-loop can be ignore once the format of 96 // add flag is invalid. 97 validateFlag(add); 98 for (String flag : flags) { 99 // Simply ignore both of remove and add flags first, then add the add flag after 100 // exiting the loop to prevent adding the duplicate flag. 101 if (remove.equals(flag) || add.equals(flag)) continue; 102 result.add(flag); 103 } 104 result.add(add); 105 return result.toArray(new String[result.size()]); 106 } catch (IllegalArgumentException iae) { 107 throw new IllegalStateException("Invalid InterfaceConfigurationParcel", iae); 108 } 109 } 110 111 /** 112 * Set interface configuration to netd by passing InterfaceConfigurationParcel. 113 */ setInterfaceConfig(INetd netd, InterfaceConfigurationParcel configParcel)114 public static void setInterfaceConfig(INetd netd, InterfaceConfigurationParcel configParcel) { 115 try { 116 netd.interfaceSetCfg(configParcel); 117 } catch (RemoteException | ServiceSpecificException e) { 118 throw new IllegalStateException(e); 119 } 120 } 121 122 /** 123 * Set the given interface up. 124 */ setInterfaceUp(INetd netd, String iface)125 public static void setInterfaceUp(INetd netd, String iface) { 126 final InterfaceConfigurationParcel configParcel = getInterfaceConfigParcel(netd, iface); 127 configParcel.flags = removeAndAddFlags(configParcel.flags, IF_STATE_DOWN /* remove */, 128 IF_STATE_UP /* add */); 129 setInterfaceConfig(netd, configParcel); 130 } 131 132 /** 133 * Set the given interface down. 134 */ setInterfaceDown(INetd netd, String iface)135 public static void setInterfaceDown(INetd netd, String iface) { 136 final InterfaceConfigurationParcel configParcel = getInterfaceConfigParcel(netd, iface); 137 configParcel.flags = removeAndAddFlags(configParcel.flags, IF_STATE_UP /* remove */, 138 IF_STATE_DOWN /* add */); 139 setInterfaceConfig(netd, configParcel); 140 } 141 142 /** Start tethering. */ tetherStart(final INetd netd, final boolean usingLegacyDnsProxy, final String[] dhcpRange)143 public static void tetherStart(final INetd netd, final boolean usingLegacyDnsProxy, 144 final String[] dhcpRange) throws RemoteException, ServiceSpecificException { 145 final TetherConfigParcel config = new TetherConfigParcel(); 146 config.usingLegacyDnsProxy = usingLegacyDnsProxy; 147 config.dhcpRanges = dhcpRange; 148 netd.tetherStartWithConfiguration(config); 149 } 150 151 /** Setup interface for tethering. */ tetherInterface(final INetd netd, final String iface, final IpPrefix dest)152 public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest) 153 throws RemoteException, ServiceSpecificException { 154 tetherInterface(netd, iface, dest, 20 /* maxAttempts */, 50 /* pollingIntervalMs */); 155 } 156 157 /** Setup interface with configurable retries for tethering. */ tetherInterface(final INetd netd, final String iface, final IpPrefix dest, int maxAttempts, int pollingIntervalMs)158 public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest, 159 int maxAttempts, int pollingIntervalMs) 160 throws RemoteException, ServiceSpecificException { 161 netd.tetherInterfaceAdd(iface); 162 networkAddInterface(netd, iface, maxAttempts, pollingIntervalMs); 163 // Activate a route to dest and IPv6 link local. 164 modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID, 165 new RouteInfo(dest, null, iface, RTN_UNICAST)); 166 modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID, 167 new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST)); 168 } 169 170 /** 171 * Retry Netd#networkAddInterface for EBUSY error code. 172 * If the same interface (e.g., wlan0) is in client mode and then switches to tethered mode. 173 * There can be a race where puts the interface into the local network but interface is still 174 * in use in netd because the ConnectivityService thread hasn't processed the disconnect yet. 175 * See b/158269544 for detail. 176 */ networkAddInterface(final INetd netd, final String iface, int maxAttempts, int pollingIntervalMs)177 private static void networkAddInterface(final INetd netd, final String iface, 178 int maxAttempts, int pollingIntervalMs) 179 throws ServiceSpecificException, RemoteException { 180 for (int i = 1; i <= maxAttempts; i++) { 181 try { 182 netd.networkAddInterface(INetd.LOCAL_NET_ID, iface); 183 return; 184 } catch (ServiceSpecificException e) { 185 if (e.errorCode == EBUSY && i < maxAttempts) { 186 SystemClock.sleep(pollingIntervalMs); 187 continue; 188 } 189 190 Log.e(TAG, "Retry Netd#networkAddInterface failure: " + e); 191 throw e; 192 } 193 } 194 } 195 196 /** Reset interface for tethering. */ untetherInterface(final INetd netd, String iface)197 public static void untetherInterface(final INetd netd, String iface) 198 throws RemoteException, ServiceSpecificException { 199 try { 200 netd.tetherInterfaceRemove(iface); 201 } finally { 202 netd.networkRemoveInterface(INetd.LOCAL_NET_ID, iface); 203 } 204 } 205 206 /** Add |routes| to local network. */ addRoutesToLocalNetwork(final INetd netd, final String iface, final List<RouteInfo> routes)207 public static void addRoutesToLocalNetwork(final INetd netd, final String iface, 208 final List<RouteInfo> routes) { 209 210 for (RouteInfo route : routes) { 211 if (!route.isDefaultRoute()) { 212 modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID, route); 213 } 214 } 215 216 // IPv6 link local should be activated always. 217 modifyRoute(netd, ModifyOperation.ADD, INetd.LOCAL_NET_ID, 218 new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST)); 219 } 220 221 /** Remove routes from local network. */ removeRoutesFromLocalNetwork(final INetd netd, final List<RouteInfo> routes)222 public static int removeRoutesFromLocalNetwork(final INetd netd, final List<RouteInfo> routes) { 223 int failures = 0; 224 225 for (RouteInfo route : routes) { 226 try { 227 modifyRoute(netd, ModifyOperation.REMOVE, INetd.LOCAL_NET_ID, route); 228 } catch (IllegalStateException e) { 229 failures++; 230 } 231 } 232 233 return failures; 234 } 235 236 @SuppressLint("NewApi") findNextHop(final RouteInfo route)237 private static String findNextHop(final RouteInfo route) { 238 final String nextHop; 239 switch (route.getType()) { 240 case RTN_UNICAST: 241 if (route.hasGateway()) { 242 nextHop = route.getGateway().getHostAddress(); 243 } else { 244 nextHop = INetd.NEXTHOP_NONE; 245 } 246 break; 247 case RTN_UNREACHABLE: 248 nextHop = INetd.NEXTHOP_UNREACHABLE; 249 break; 250 case RTN_THROW: 251 nextHop = INetd.NEXTHOP_THROW; 252 break; 253 default: 254 nextHop = INetd.NEXTHOP_NONE; 255 break; 256 } 257 return nextHop; 258 } 259 260 /** Add or remove |route|. */ modifyRoute(final INetd netd, final ModifyOperation op, final int netId, final RouteInfo route)261 public static void modifyRoute(final INetd netd, final ModifyOperation op, final int netId, 262 final RouteInfo route) { 263 final String ifName = route.getInterface(); 264 final String dst = route.getDestination().toString(); 265 final String nextHop = findNextHop(route); 266 267 try { 268 switch(op) { 269 case ADD: 270 netd.networkAddRoute(netId, ifName, dst, nextHop); 271 break; 272 case REMOVE: 273 netd.networkRemoveRoute(netId, ifName, dst, nextHop); 274 break; 275 default: 276 throw new IllegalStateException("Unsupported modify operation:" + op); 277 } 278 } catch (RemoteException | ServiceSpecificException e) { 279 throw new IllegalStateException(e); 280 } 281 } 282 283 /** 284 * Convert a RouteInfo into a RouteInfoParcel. 285 */ toRouteInfoParcel(RouteInfo route)286 public static RouteInfoParcel toRouteInfoParcel(RouteInfo route) { 287 final String nextHop; 288 289 switch (route.getType()) { 290 case RouteInfo.RTN_UNICAST: 291 if (route.hasGateway()) { 292 nextHop = route.getGateway().getHostAddress(); 293 } else { 294 nextHop = INetd.NEXTHOP_NONE; 295 } 296 break; 297 case RouteInfo.RTN_UNREACHABLE: 298 nextHop = INetd.NEXTHOP_UNREACHABLE; 299 break; 300 case RouteInfo.RTN_THROW: 301 nextHop = INetd.NEXTHOP_THROW; 302 break; 303 default: 304 nextHop = INetd.NEXTHOP_NONE; 305 break; 306 } 307 308 final RouteInfoParcel rip = new RouteInfoParcel(); 309 rip.ifName = route.getInterface(); 310 rip.destination = route.getDestination().toString(); 311 rip.nextHop = nextHop; 312 rip.mtu = route.getMtu(); 313 314 return rip; 315 } 316 } 317