• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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