1 /*
2  * Copyright (C) 2014 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.server.ethernet;
18 
19 import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable;
20 import static com.android.internal.util.Preconditions.checkNotNull;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.content.Context;
25 import android.net.ConnectivityManager;
26 import android.net.IpConfiguration;
27 import android.net.IpConfiguration.IpAssignment;
28 import android.net.IpConfiguration.ProxySettings;
29 import android.net.LinkProperties;
30 import android.net.NetworkAgent;
31 import android.net.NetworkAgentConfig;
32 import android.net.NetworkCapabilities;
33 import android.net.NetworkFactory;
34 import android.net.NetworkRequest;
35 import android.net.NetworkSpecifier;
36 import android.net.StringNetworkSpecifier;
37 import android.net.ip.IIpClient;
38 import android.net.ip.IpClientCallbacks;
39 import android.net.ip.IpClientUtil;
40 import android.net.shared.ProvisioningConfiguration;
41 import android.net.util.InterfaceParams;
42 import android.os.ConditionVariable;
43 import android.os.Handler;
44 import android.os.RemoteException;
45 import android.text.TextUtils;
46 import android.util.AndroidRuntimeException;
47 import android.util.Log;
48 import android.util.SparseArray;
49 
50 import com.android.internal.util.IndentingPrintWriter;
51 
52 import java.io.FileDescriptor;
53 import java.util.concurrent.ConcurrentHashMap;
54 import java.util.Objects;
55 
56 /**
57  * {@link NetworkFactory} that represents Ethernet networks.
58  *
59  * This class reports a static network score of 70 when it is tracking an interface and that
60  * interface's link is up, and a score of 0 otherwise.
61  */
62 public class EthernetNetworkFactory extends NetworkFactory {
63     private final static String TAG = EthernetNetworkFactory.class.getSimpleName();
64     final static boolean DBG = true;
65 
66     private final static int NETWORK_SCORE = 70;
67     private static final String NETWORK_TYPE = "Ethernet";
68 
69     private final ConcurrentHashMap<String, NetworkInterfaceState> mTrackingInterfaces =
70             new ConcurrentHashMap<>();
71     private final Handler mHandler;
72     private final Context mContext;
73 
74     public static class ConfigurationException extends AndroidRuntimeException {
ConfigurationException(String msg)75         public ConfigurationException(String msg) {
76             super(msg);
77         }
78     }
79 
EthernetNetworkFactory(Handler handler, Context context, NetworkCapabilities filter)80     public EthernetNetworkFactory(Handler handler, Context context, NetworkCapabilities filter) {
81         super(handler.getLooper(), context, NETWORK_TYPE, filter);
82 
83         mHandler = handler;
84         mContext = context;
85 
86         setScoreFilter(NETWORK_SCORE);
87     }
88 
89     @Override
acceptRequest(NetworkRequest request, int score)90     public boolean acceptRequest(NetworkRequest request, int score) {
91         if (request.type == NetworkRequest.Type.TRACK_DEFAULT) {
92             return false;
93         }
94 
95         if (DBG) {
96             Log.d(TAG, "acceptRequest, request: " + request + ", score: " + score);
97         }
98 
99         return networkForRequest(request) != null;
100     }
101 
102     @Override
needNetworkFor(NetworkRequest networkRequest, int score)103     protected void needNetworkFor(NetworkRequest networkRequest, int score) {
104         NetworkInterfaceState network = networkForRequest(networkRequest);
105 
106         if (network == null) {
107             Log.e(TAG, "needNetworkFor, failed to get a network for " + networkRequest);
108             return;
109         }
110 
111         if (++network.refCount == 1) {
112             network.start();
113         }
114     }
115 
116     @Override
releaseNetworkFor(NetworkRequest networkRequest)117     protected void releaseNetworkFor(NetworkRequest networkRequest) {
118         NetworkInterfaceState network = networkForRequest(networkRequest);
119         if (network == null) {
120             Log.e(TAG, "releaseNetworkFor, failed to get a network for " + networkRequest);
121             return;
122         }
123 
124         if (--network.refCount == 0) {
125             network.stop();
126         }
127     }
128 
129     /**
130      * Returns an array of available interface names. The array is sorted: unrestricted interfaces
131      * goes first, then sorted by name.
132      */
getAvailableInterfaces(boolean includeRestricted)133     String[] getAvailableInterfaces(boolean includeRestricted) {
134         return mTrackingInterfaces.values()
135                 .stream()
136                 .filter(iface -> !iface.isRestricted() || includeRestricted)
137                 .sorted((iface1, iface2) -> {
138                     int r = Boolean.compare(iface1.isRestricted(), iface2.isRestricted());
139                     return r == 0 ? iface1.name.compareTo(iface2.name) : r;
140                 })
141                 .map(iface -> iface.name)
142                 .toArray(String[]::new);
143     }
144 
addInterface(String ifaceName, String hwAddress, NetworkCapabilities capabilities, IpConfiguration ipConfiguration)145     void addInterface(String ifaceName, String hwAddress, NetworkCapabilities capabilities,
146              IpConfiguration ipConfiguration) {
147         if (mTrackingInterfaces.containsKey(ifaceName)) {
148             Log.e(TAG, "Interface with name " + ifaceName + " already exists.");
149             return;
150         }
151 
152         if (DBG) {
153             Log.d(TAG, "addInterface, iface: " + ifaceName + ", capabilities: " + capabilities);
154         }
155 
156         NetworkInterfaceState iface = new NetworkInterfaceState(
157                 ifaceName, hwAddress, mHandler, mContext, capabilities, this);
158         iface.setIpConfig(ipConfiguration);
159         mTrackingInterfaces.put(ifaceName, iface);
160 
161         updateCapabilityFilter();
162     }
163 
updateCapabilityFilter()164     private void updateCapabilityFilter() {
165         NetworkCapabilities capabilitiesFilter = new NetworkCapabilities();
166         capabilitiesFilter.clearAll();
167 
168         for (NetworkInterfaceState iface:  mTrackingInterfaces.values()) {
169             capabilitiesFilter.combineCapabilities(iface.mCapabilities);
170         }
171 
172         if (DBG) Log.d(TAG, "updateCapabilityFilter: " + capabilitiesFilter);
173         setCapabilityFilter(capabilitiesFilter);
174     }
175 
removeInterface(String interfaceName)176     void removeInterface(String interfaceName) {
177         NetworkInterfaceState iface = mTrackingInterfaces.remove(interfaceName);
178         if (iface != null) {
179             iface.stop();
180         }
181 
182         updateCapabilityFilter();
183     }
184 
185     /** Returns true if state has been modified */
updateInterfaceLinkState(String ifaceName, boolean up)186     boolean updateInterfaceLinkState(String ifaceName, boolean up) {
187         if (!mTrackingInterfaces.containsKey(ifaceName)) {
188             return false;
189         }
190 
191         if (DBG) {
192             Log.d(TAG, "updateInterfaceLinkState, iface: " + ifaceName + ", up: " + up);
193         }
194 
195         NetworkInterfaceState iface = mTrackingInterfaces.get(ifaceName);
196         return iface.updateLinkState(up);
197     }
198 
hasInterface(String interfacName)199     boolean hasInterface(String interfacName) {
200         return mTrackingInterfaces.containsKey(interfacName);
201     }
202 
updateIpConfiguration(String iface, IpConfiguration ipConfiguration)203     void updateIpConfiguration(String iface, IpConfiguration ipConfiguration) {
204         NetworkInterfaceState network = mTrackingInterfaces.get(iface);
205         if (network != null) {
206             network.setIpConfig(ipConfiguration);
207         }
208     }
209 
networkForRequest(NetworkRequest request)210     private NetworkInterfaceState networkForRequest(NetworkRequest request) {
211         String requestedIface = null;
212 
213         NetworkSpecifier specifier = request.networkCapabilities.getNetworkSpecifier();
214         if (specifier instanceof StringNetworkSpecifier) {
215             requestedIface = ((StringNetworkSpecifier) specifier).specifier;
216         }
217 
218         NetworkInterfaceState network = null;
219         if (!TextUtils.isEmpty(requestedIface)) {
220             NetworkInterfaceState n = mTrackingInterfaces.get(requestedIface);
221             if (n != null && n.satisfied(request.networkCapabilities)) {
222                 network = n;
223             }
224         } else {
225             for (NetworkInterfaceState n : mTrackingInterfaces.values()) {
226                 if (n.satisfied(request.networkCapabilities) && n.mLinkUp) {
227                     network = n;
228                     break;
229                 }
230             }
231         }
232 
233         if (DBG) {
234             Log.i(TAG, "networkForRequest, request: " + request + ", network: " + network);
235         }
236 
237         return network;
238     }
239 
240     private static class NetworkInterfaceState {
241         final String name;
242 
243         private final String mHwAddress;
244         private final NetworkCapabilities mCapabilities;
245         private final Handler mHandler;
246         private final Context mContext;
247         private final NetworkFactory mNetworkFactory;
248         private final int mLegacyType;
249 
250         private static String sTcpBufferSizes = null;  // Lazy initialized.
251 
252         private boolean mLinkUp;
253         private LinkProperties mLinkProperties = new LinkProperties();
254 
255         private volatile @Nullable IIpClient mIpClient;
256         private @Nullable IpClientCallbacksImpl mIpClientCallback;
257         private @Nullable NetworkAgent mNetworkAgent;
258         private @Nullable IpConfiguration mIpConfig;
259 
260         /**
261          * An object to contain all transport type information, including base network score and
262          * the legacy transport type it maps to (if any)
263          */
264         private static class TransportInfo {
265             final int mLegacyType;
266             final int mScore;
267 
TransportInfo(int legacyType, int score)268             private TransportInfo(int legacyType, int score) {
269                 mLegacyType = legacyType;
270                 mScore = score;
271             }
272         }
273 
274         /**
275          * A map of TRANSPORT_* types to TransportInfo, making scoring and legacy type information
276          * available for each type an ethernet interface could propagate.
277          *
278          * Unfortunately, base scores for the various transports are not yet centrally located.
279          * They've been lifted from the corresponding NetworkFactory files in the meantime.
280          *
281          * Additionally, there are no legacy type equivalents to LOWPAN or WIFI_AWARE. These types
282          * are set to TYPE_NONE to match the behavior of their own network factories.
283          */
284         private static final SparseArray<TransportInfo> sTransports = new SparseArray();
285         static {
286             // LowpanInterfaceTracker.NETWORK_SCORE
sTransports.put(NetworkCapabilities.TRANSPORT_LOWPAN, new TransportInfo(ConnectivityManager.TYPE_NONE, 30))287             sTransports.put(NetworkCapabilities.TRANSPORT_LOWPAN,
288                     new TransportInfo(ConnectivityManager.TYPE_NONE, 30));
289             // WifiAwareDataPathStateManager.NETWORK_FACTORY_SCORE_AVAIL
sTransports.put(NetworkCapabilities.TRANSPORT_WIFI_AWARE, new TransportInfo(ConnectivityManager.TYPE_NONE, 1))290             sTransports.put(NetworkCapabilities.TRANSPORT_WIFI_AWARE,
291                     new TransportInfo(ConnectivityManager.TYPE_NONE, 1));
292             // EthernetNetworkFactory.NETWORK_SCORE
sTransports.put(NetworkCapabilities.TRANSPORT_ETHERNET, new TransportInfo(ConnectivityManager.TYPE_ETHERNET, 70))293             sTransports.put(NetworkCapabilities.TRANSPORT_ETHERNET,
294                     new TransportInfo(ConnectivityManager.TYPE_ETHERNET, 70));
295             // BluetoothTetheringNetworkFactory.NETWORK_SCORE
sTransports.put(NetworkCapabilities.TRANSPORT_BLUETOOTH, new TransportInfo(ConnectivityManager.TYPE_BLUETOOTH, 69))296             sTransports.put(NetworkCapabilities.TRANSPORT_BLUETOOTH,
297                     new TransportInfo(ConnectivityManager.TYPE_BLUETOOTH, 69));
298             // WifiNetworkFactory.SCORE_FILTER / NetworkAgent.WIFI_BASE_SCORE
sTransports.put(NetworkCapabilities.TRANSPORT_WIFI, new TransportInfo(ConnectivityManager.TYPE_WIFI, 60))299             sTransports.put(NetworkCapabilities.TRANSPORT_WIFI,
300                     new TransportInfo(ConnectivityManager.TYPE_WIFI, 60));
301             // TelephonyNetworkFactory.TELEPHONY_NETWORK_SCORE
sTransports.put(NetworkCapabilities.TRANSPORT_CELLULAR, new TransportInfo(ConnectivityManager.TYPE_MOBILE, 50))302             sTransports.put(NetworkCapabilities.TRANSPORT_CELLULAR,
303                     new TransportInfo(ConnectivityManager.TYPE_MOBILE, 50));
304         }
305 
306         long refCount = 0;
307 
308         private class IpClientCallbacksImpl extends IpClientCallbacks {
309             private final ConditionVariable mIpClientStartCv = new ConditionVariable(false);
310             private final ConditionVariable mIpClientShutdownCv = new ConditionVariable(false);
311 
312             @Override
onIpClientCreated(IIpClient ipClient)313             public void onIpClientCreated(IIpClient ipClient) {
314                 mIpClient = ipClient;
315                 mIpClientStartCv.open();
316             }
317 
awaitIpClientStart()318             private void awaitIpClientStart() {
319                 mIpClientStartCv.block();
320             }
321 
awaitIpClientShutdown()322             private void awaitIpClientShutdown() {
323                 mIpClientShutdownCv.block();
324             }
325 
326             @Override
onProvisioningSuccess(LinkProperties newLp)327             public void onProvisioningSuccess(LinkProperties newLp) {
328                 mHandler.post(() -> onIpLayerStarted(newLp));
329             }
330 
331             @Override
onProvisioningFailure(LinkProperties newLp)332             public void onProvisioningFailure(LinkProperties newLp) {
333                 mHandler.post(() -> onIpLayerStopped(newLp));
334             }
335 
336             @Override
onLinkPropertiesChange(LinkProperties newLp)337             public void onLinkPropertiesChange(LinkProperties newLp) {
338                 mHandler.post(() -> updateLinkProperties(newLp));
339             }
340 
341             @Override
onQuit()342             public void onQuit() {
343                 mIpClient = null;
344                 mIpClientShutdownCv.open();
345             }
346         }
347 
shutdownIpClient(IIpClient ipClient)348         private static void shutdownIpClient(IIpClient ipClient) {
349             try {
350                 ipClient.shutdown();
351             } catch (RemoteException e) {
352                 Log.e(TAG, "Error stopping IpClient", e);
353             }
354         }
355 
NetworkInterfaceState(String ifaceName, String hwAddress, Handler handler, Context context, @NonNull NetworkCapabilities capabilities, NetworkFactory networkFactory)356         NetworkInterfaceState(String ifaceName, String hwAddress, Handler handler, Context context,
357                 @NonNull NetworkCapabilities capabilities, NetworkFactory networkFactory) {
358             name = ifaceName;
359             mCapabilities = checkNotNull(capabilities);
360             mHandler = handler;
361             mContext = context;
362             mNetworkFactory = networkFactory;
363             int legacyType = ConnectivityManager.TYPE_NONE;
364             int[] transportTypes = mCapabilities.getTransportTypes();
365 
366             if (transportTypes.length > 0) {
367                 legacyType = getLegacyType(transportTypes[0]);
368             } else {
369                 // Should never happen as transport is always one of ETHERNET or a valid override
370                 throw new ConfigurationException("Network Capabilities do not have an associated "
371                         + "transport type.");
372             }
373 
374             mHwAddress = hwAddress;
375             mLegacyType = legacyType;
376         }
377 
setIpConfig(IpConfiguration ipConfig)378         void setIpConfig(IpConfiguration ipConfig) {
379             if (Objects.equals(this.mIpConfig, ipConfig)) {
380                 if (DBG) Log.d(TAG, "ipConfig have not changed,so ignore setIpConfig");
381                 return;
382             }
383             this.mIpConfig = ipConfig;
384             if (mNetworkAgent != null) {
385                 restart();
386             }
387         }
388 
satisfied(NetworkCapabilities requestedCapabilities)389         boolean satisfied(NetworkCapabilities requestedCapabilities) {
390             return requestedCapabilities.satisfiedByNetworkCapabilities(mCapabilities);
391         }
392 
isRestricted()393         boolean isRestricted() {
394             return !mCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
395         }
396 
397         /**
398          * Determines the legacy transport type from a NetworkCapabilities transport type. Defaults
399          * to legacy TYPE_NONE if there is no known conversion
400          */
getLegacyType(int transport)401         private static int getLegacyType(int transport) {
402             TransportInfo transportInfo = sTransports.get(transport, /* if dne */ null);
403             if (transportInfo != null) {
404                 return transportInfo.mLegacyType;
405             }
406             return ConnectivityManager.TYPE_NONE;
407         }
408 
409         /**
410          * Determines the network score based on the transport associated with the interface.
411          * Ethernet interfaces could propagate a transport types forward. Since we can't
412          * get more information about the statuses of the interfaces on the other end of the local
413          * interface, we'll best-effort assign the score as the base score of the assigned transport
414          * when the link is up. When the link is down, the score is set to zero.
415          *
416          * This function is called with the purpose of assigning and updating the network score of
417          * the member NetworkAgent.
418          */
getNetworkScore()419         private int getNetworkScore() {
420             // never set the network score below 0.
421             if (!mLinkUp) {
422                 return 0;
423             }
424 
425             int[] transportTypes = mCapabilities.getTransportTypes();
426             if (transportTypes.length < 1) {
427                 Log.w(TAG, "Network interface '" + mLinkProperties.getInterfaceName() + "' has no "
428                         + "transport type associated with it. Score set to zero");
429                 return 0;
430             }
431             TransportInfo transportInfo = sTransports.get(transportTypes[0], /* if dne */ null);
432             if (transportInfo != null) {
433                 return transportInfo.mScore;
434             }
435             return 0;
436         }
437 
start()438         private void start() {
439             if (mIpClient != null) {
440                 if (DBG) Log.d(TAG, "IpClient already started");
441                 return;
442             }
443             if (DBG) {
444                 Log.d(TAG, String.format("Starting Ethernet IpClient(%s)", name));
445             }
446 
447             mIpClientCallback = new IpClientCallbacksImpl();
448             IpClientUtil.makeIpClient(mContext, name, mIpClientCallback);
449             mIpClientCallback.awaitIpClientStart();
450             if (sTcpBufferSizes == null) {
451                 sTcpBufferSizes = mContext.getResources().getString(
452                         com.android.internal.R.string.config_ethernet_tcp_buffers);
453             }
454             provisionIpClient(mIpClient, mIpConfig, sTcpBufferSizes);
455         }
456 
onIpLayerStarted(LinkProperties linkProperties)457         void onIpLayerStarted(LinkProperties linkProperties) {
458             if (mNetworkAgent != null) {
459                 Log.e(TAG, "Already have a NetworkAgent - aborting new request");
460                 stop();
461                 return;
462             }
463             mLinkProperties = linkProperties;
464 
465             // Create our NetworkAgent.
466             final NetworkAgentConfig config = new NetworkAgentConfig.Builder()
467                     .setLegacyType(mLegacyType)
468                     .setLegacyTypeName(NETWORK_TYPE)
469                     .build();
470             mNetworkAgent = new NetworkAgent(mContext, mHandler.getLooper(),
471                     NETWORK_TYPE, mCapabilities, mLinkProperties,
472                     getNetworkScore(), config, mNetworkFactory.getProvider()) {
473                 public void unwanted() {
474                     if (this == mNetworkAgent) {
475                         stop();
476                     } else if (mNetworkAgent != null) {
477                         Log.d(TAG, "Ignoring unwanted as we have a more modern " +
478                                 "instance");
479                     }  // Otherwise, we've already called stop.
480                 }
481             };
482             mNetworkAgent.register();
483             mNetworkAgent.setLegacyExtraInfo(mHwAddress);
484             mNetworkAgent.markConnected();
485         }
486 
onIpLayerStopped(LinkProperties linkProperties)487         void onIpLayerStopped(LinkProperties linkProperties) {
488             // This cannot happen due to provisioning timeout, because our timeout is 0. It can only
489             // happen if we're provisioned and we lose provisioning.
490             stop();
491             // If the interface has disappeared provisioning will fail over and over again, so
492             // there is no point in starting again
493             if (null != InterfaceParams.getByName(name)) {
494                 start();
495             }
496         }
497 
updateLinkProperties(LinkProperties linkProperties)498         void updateLinkProperties(LinkProperties linkProperties) {
499             mLinkProperties = linkProperties;
500             if (mNetworkAgent != null) {
501                 mNetworkAgent.sendLinkProperties(linkProperties);
502             }
503         }
504 
505         /** Returns true if state has been modified */
updateLinkState(boolean up)506         boolean updateLinkState(boolean up) {
507             if (mLinkUp == up) return false;
508             mLinkUp = up;
509 
510             stop();
511             if (up) {
512                 start();
513             }
514 
515             return true;
516         }
517 
stop()518         void stop() {
519             // Invalidate all previous start requests
520             if (mIpClient != null) {
521                 shutdownIpClient(mIpClient);
522                 mIpClientCallback.awaitIpClientShutdown();
523                 mIpClient = null;
524             }
525             mIpClientCallback = null;
526 
527             if (mNetworkAgent != null) {
528                 mNetworkAgent.unregister();
529                 mNetworkAgent = null;
530             }
531             mLinkProperties.clear();
532         }
533 
updateAgent()534         private void updateAgent() {
535             if (mNetworkAgent == null) return;
536             if (DBG) {
537                 Log.i(TAG, "Updating mNetworkAgent with: " +
538                         mCapabilities + ", " +
539                         mLinkProperties);
540             }
541             mNetworkAgent.sendNetworkCapabilities(mCapabilities);
542             mNetworkAgent.sendLinkProperties(mLinkProperties);
543 
544             // As a note, getNetworkScore() is fairly expensive to calculate. This is fine for now
545             // since the agent isn't updated frequently. Consider caching the score in the future if
546             // agent updating is required more often
547             mNetworkAgent.sendNetworkScore(getNetworkScore());
548         }
549 
provisionIpClient(IIpClient ipClient, IpConfiguration config, String tcpBufferSizes)550         private static void provisionIpClient(IIpClient ipClient, IpConfiguration config,
551                 String tcpBufferSizes) {
552             if (config.getProxySettings() == ProxySettings.STATIC ||
553                     config.getProxySettings() == ProxySettings.PAC) {
554                 try {
555                     ipClient.setHttpProxy(toStableParcelable(config.getHttpProxy()));
556                 } catch (RemoteException e) {
557                     e.rethrowFromSystemServer();
558                 }
559             }
560 
561             if (!TextUtils.isEmpty(tcpBufferSizes)) {
562                 try {
563                     ipClient.setTcpBufferSizes(tcpBufferSizes);
564                 } catch (RemoteException e) {
565                     e.rethrowFromSystemServer();
566                 }
567             }
568 
569             final ProvisioningConfiguration provisioningConfiguration;
570             if (config.getIpAssignment() == IpAssignment.STATIC) {
571                 provisioningConfiguration = new ProvisioningConfiguration.Builder()
572                         .withStaticConfiguration(config.getStaticIpConfiguration())
573                         .build();
574             } else {
575                 provisioningConfiguration = new ProvisioningConfiguration.Builder()
576                         .withProvisioningTimeoutMs(0)
577                         .build();
578             }
579 
580             try {
581                 ipClient.startProvisioning(provisioningConfiguration.toStableParcelable());
582             } catch (RemoteException e) {
583                 e.rethrowFromSystemServer();
584             }
585         }
586 
restart()587         void restart(){
588             if (DBG) Log.d(TAG, "reconnecting Etherent");
589             stop();
590             start();
591         }
592 
593         @Override
toString()594         public String toString() {
595             return getClass().getSimpleName() + "{ "
596                     + "refCount: " + refCount + ", "
597                     + "iface: " + name + ", "
598                     + "up: " + mLinkUp + ", "
599                     + "hwAddress: " + mHwAddress + ", "
600                     + "networkCapabilities: " + mCapabilities + ", "
601                     + "networkAgent: " + mNetworkAgent + ", "
602                     + "score: " + getNetworkScore() + ", "
603                     + "ipClient: " + mIpClient + ","
604                     + "linkProperties: " + mLinkProperties
605                     + "}";
606         }
607     }
608 
dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args)609     void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
610         super.dump(fd, pw, args);
611         pw.println(getClass().getSimpleName());
612         pw.println("Tracking interfaces:");
613         pw.increaseIndent();
614         for (String iface: mTrackingInterfaces.keySet()) {
615             NetworkInterfaceState ifaceState = mTrackingInterfaces.get(iface);
616             pw.println(iface + ":" + ifaceState);
617             pw.increaseIndent();
618             final IIpClient ipClient = ifaceState.mIpClient;
619             if (ipClient != null) {
620                 IpClientUtil.dumpIpClient(ipClient, fd, pw, args);
621             } else {
622                 pw.println("IpClient is null");
623             }
624             pw.decreaseIndent();
625         }
626         pw.decreaseIndent();
627     }
628 }
629