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 /** 152 * Retry Netd#networkAddInterface for EBUSY error code. 153 * If the same interface (e.g., wlan0) is in client mode and then switches to tethered mode. 154 * There can be a race where puts the interface into the local network but interface is still 155 * in use in netd because the ConnectivityService thread hasn't processed the disconnect yet. 156 * See b/158269544 for detail. 157 */ networkAddInterface(final INetd netd, int netId, final String iface, int maxAttempts, int pollingIntervalMs)158 public static void networkAddInterface(final INetd netd, int netId, final String iface, 159 int maxAttempts, int pollingIntervalMs) 160 throws ServiceSpecificException, RemoteException { 161 for (int i = 1; i <= maxAttempts; i++) { 162 try { 163 netd.networkAddInterface(netId, iface); 164 return; 165 } catch (ServiceSpecificException e) { 166 if (e.errorCode == EBUSY && i < maxAttempts) { 167 SystemClock.sleep(pollingIntervalMs); 168 continue; 169 } 170 171 Log.e(TAG, "Retry Netd#networkAddInterface failure: " + e); 172 throw e; 173 } 174 } 175 } 176 177 /** Add |routes| to the given network. */ addRoutesToNetwork(final INetd netd, int netId, final String iface, final List<RouteInfo> routes)178 public static void addRoutesToNetwork(final INetd netd, int netId, final String iface, 179 final List<RouteInfo> routes) { 180 181 for (RouteInfo route : routes) { 182 if (!route.isDefaultRoute()) { 183 modifyRoute(netd, ModifyOperation.ADD, netId, route); 184 } 185 } 186 187 // IPv6 link local should be activated always. 188 modifyRoute(netd, ModifyOperation.ADD, netId, 189 new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST)); 190 } 191 192 /** Remove routes from the given network. */ removeRoutesFromNetwork(final INetd netd, int netId, final List<RouteInfo> routes)193 public static int removeRoutesFromNetwork(final INetd netd, int netId, 194 final List<RouteInfo> routes) { 195 int failures = 0; 196 197 for (RouteInfo route : routes) { 198 try { 199 modifyRoute(netd, ModifyOperation.REMOVE, netId, route); 200 } catch (IllegalStateException e) { 201 failures++; 202 } 203 } 204 205 return failures; 206 } 207 208 @SuppressLint("NewApi") findNextHop(final RouteInfo route)209 private static String findNextHop(final RouteInfo route) { 210 final String nextHop; 211 switch (route.getType()) { 212 case RTN_UNICAST: 213 if (route.hasGateway()) { 214 nextHop = route.getGateway().getHostAddress(); 215 } else { 216 nextHop = INetd.NEXTHOP_NONE; 217 } 218 break; 219 case RTN_UNREACHABLE: 220 nextHop = INetd.NEXTHOP_UNREACHABLE; 221 break; 222 case RTN_THROW: 223 nextHop = INetd.NEXTHOP_THROW; 224 break; 225 default: 226 nextHop = INetd.NEXTHOP_NONE; 227 break; 228 } 229 return nextHop; 230 } 231 232 /** Add or remove |route|. */ modifyRoute(final INetd netd, final ModifyOperation op, final int netId, final RouteInfo route)233 public static void modifyRoute(final INetd netd, final ModifyOperation op, final int netId, 234 final RouteInfo route) { 235 final String ifName = route.getInterface(); 236 final String dst = route.getDestination().toString(); 237 final String nextHop = findNextHop(route); 238 239 try { 240 switch(op) { 241 case ADD: 242 netd.networkAddRoute(netId, ifName, dst, nextHop); 243 break; 244 case REMOVE: 245 netd.networkRemoveRoute(netId, ifName, dst, nextHop); 246 break; 247 default: 248 throw new IllegalStateException("Unsupported modify operation:" + op); 249 } 250 } catch (RemoteException | ServiceSpecificException e) { 251 throw new IllegalStateException(e); 252 } 253 } 254 255 /** 256 * Convert a RouteInfo into a RouteInfoParcel. 257 */ toRouteInfoParcel(RouteInfo route)258 public static RouteInfoParcel toRouteInfoParcel(RouteInfo route) { 259 final String nextHop; 260 261 switch (route.getType()) { 262 case RouteInfo.RTN_UNICAST: 263 if (route.hasGateway()) { 264 nextHop = route.getGateway().getHostAddress(); 265 } else { 266 nextHop = INetd.NEXTHOP_NONE; 267 } 268 break; 269 case RouteInfo.RTN_UNREACHABLE: 270 nextHop = INetd.NEXTHOP_UNREACHABLE; 271 break; 272 case RouteInfo.RTN_THROW: 273 nextHop = INetd.NEXTHOP_THROW; 274 break; 275 default: 276 nextHop = INetd.NEXTHOP_NONE; 277 break; 278 } 279 280 final RouteInfoParcel rip = new RouteInfoParcel(); 281 rip.ifName = route.getInterface(); 282 rip.destination = route.getDestination().toString(); 283 rip.nextHop = nextHop; 284 rip.mtu = route.getMtu(); 285 286 return rip; 287 } 288 } 289