• 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.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