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