• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.internal.telephony.dataconnection;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.net.KeepalivePacketData;
22 import android.net.LinkProperties;
23 import android.net.NattKeepalivePacketData;
24 import android.net.NetworkAgent;
25 import android.net.NetworkAgentConfig;
26 import android.net.NetworkCapabilities;
27 import android.net.NetworkInfo;
28 import android.net.NetworkProvider;
29 import android.net.SocketKeepalive;
30 import android.net.Uri;
31 import android.os.Message;
32 import android.telephony.AccessNetworkConstants;
33 import android.telephony.AccessNetworkConstants.TransportType;
34 import android.telephony.AnomalyReporter;
35 import android.telephony.TelephonyManager;
36 import android.util.ArrayMap;
37 import android.util.LocalLog;
38 import android.util.SparseArray;
39 
40 import com.android.internal.telephony.DctConstants;
41 import com.android.internal.telephony.Phone;
42 import com.android.internal.telephony.RILConstants;
43 import com.android.internal.telephony.metrics.TelephonyMetrics;
44 import com.android.internal.util.IndentingPrintWriter;
45 import com.android.telephony.Rlog;
46 
47 import java.io.FileDescriptor;
48 import java.io.PrintWriter;
49 import java.time.Duration;
50 import java.util.Map;
51 import java.util.Objects;
52 import java.util.UUID;
53 
54 /**
55  * This class represents a network agent which is communication channel between
56  * {@link DataConnection} and {@link com.android.server.ConnectivityService}. The agent is
57  * created when data connection enters {@link DataConnection.DcActiveState} until it exits that
58  * state.
59  *
60  * Note that in IWLAN handover scenario, this agent could be transferred to the new
61  * {@link DataConnection} so for a short window of time this object might be accessed by two
62  * different {@link DataConnection}. Thus each method in this class needs to be synchronized.
63  */
64 public class DcNetworkAgent extends NetworkAgent {
65     private final String mTag;
66 
67     private final int mId;
68 
69     private Phone mPhone;
70 
71     private int mTransportType;
72 
73     private NetworkCapabilities mNetworkCapabilities;
74 
75     public final DcKeepaliveTracker keepaliveTracker = new DcKeepaliveTracker();
76 
77     private DataConnection mDataConnection;
78 
79     private final LocalLog mNetCapsLocalLog = new LocalLog(50);
80 
81     private NetworkInfo mNetworkInfo;
82 
83     // For interface duplicate detection. Key is the net id, value is the interface name in string.
84     private static Map<Integer, String> sInterfaceNames = new ArrayMap<>();
85 
DcNetworkAgent(DataConnection dc, Phone phone, NetworkInfo ni, int score, NetworkAgentConfig config, NetworkProvider networkProvider, int transportType)86     DcNetworkAgent(DataConnection dc, Phone phone, NetworkInfo ni, int score,
87             NetworkAgentConfig config, NetworkProvider networkProvider, int transportType) {
88         super(phone.getContext(), dc.getHandler().getLooper(), "DcNetworkAgent",
89                 dc.getNetworkCapabilities(), dc.getLinkProperties(), score, config,
90                 networkProvider);
91         register();
92         mId = getNetwork().netId;
93         mTag = "DcNetworkAgent" + "-" + mId;
94         mPhone = phone;
95         mNetworkCapabilities = dc.getNetworkCapabilities();
96         mTransportType = transportType;
97         mDataConnection = dc;
98         mNetworkInfo = new NetworkInfo(ni);
99         setLegacySubtype(ni.getSubtype(), ni.getSubtypeName());
100         setLegacyExtraInfo(ni.getExtraInfo());
101         if (dc.getLinkProperties() != null) {
102             checkDuplicateInterface(mId, dc.getLinkProperties().getInterfaceName());
103             logd("created for data connection " + dc.getName() + ", "
104                     + dc.getLinkProperties().getInterfaceName());
105         } else {
106             loge("The connection does not have a valid link properties.");
107         }
108     }
109 
checkDuplicateInterface(int netId, @Nullable String interfaceName)110     private void checkDuplicateInterface(int netId, @Nullable String interfaceName) {
111         for (Map.Entry<Integer, String> entry: sInterfaceNames.entrySet()) {
112             if (Objects.equals(interfaceName, entry.getValue())) {
113                 String message = "Duplicate interface " + interfaceName
114                         + " is detected. DcNetworkAgent-" + entry.getKey()
115                         + " already used this interface name.";
116                 loge(message);
117                 // Using fixed UUID to avoid duplicate bugreport notification
118                 AnomalyReporter.reportAnomaly(
119                         UUID.fromString("02f3d3f6-4613-4415-b6cb-8d92c8a938a6"),
120                         message);
121                 return;
122             }
123         }
124         sInterfaceNames.put(netId, interfaceName);
125     }
126 
127     /**
128      * @return The tag
129      */
getTag()130     String getTag() {
131         return mTag;
132     }
133 
134     /**
135      * Set the data connection that owns this network agent.
136      *
137      * @param dc Data connection owning this network agent.
138      * @param transportType Transport that this data connection is on.
139      */
acquireOwnership(@onNull DataConnection dc, @TransportType int transportType)140     public synchronized void acquireOwnership(@NonNull DataConnection dc,
141                                               @TransportType int transportType) {
142         mDataConnection = dc;
143         mTransportType = transportType;
144         logd(dc.getName() + " acquired the ownership of this agent.");
145     }
146 
147     /**
148      * Release the ownership of network agent.
149      */
releaseOwnership(DataConnection dc)150     public synchronized void releaseOwnership(DataConnection dc) {
151         if (mDataConnection == null) {
152             loge("releaseOwnership called on no-owner DcNetworkAgent!");
153             return;
154         } else if (mDataConnection != dc) {
155             loge("releaseOwnership: This agent belongs to "
156                     + mDataConnection.getName() + ", ignored the request from " + dc.getName());
157             return;
158         }
159         logd("Data connection " + mDataConnection.getName() + " released the ownership.");
160         mDataConnection = null;
161     }
162 
163     /**
164      * @return The data connection that owns this agent
165      */
getDataConnection()166     public synchronized DataConnection getDataConnection() {
167         return mDataConnection;
168     }
169 
170     @Override
onNetworkUnwanted()171     public synchronized void onNetworkUnwanted() {
172         if (mDataConnection == null) {
173             loge("onNetworkUnwanted found called on no-owner DcNetworkAgent!");
174             return;
175         }
176 
177         logd("onNetworkUnwanted called. Now tear down the data connection "
178                 + mDataConnection.getName());
179         mDataConnection.tearDownAll(Phone.REASON_RELEASED_BY_CONNECTIVITY_SERVICE,
180                 DcTracker.RELEASE_TYPE_DETACH, null);
181     }
182 
183     @Override
onBandwidthUpdateRequested()184     public synchronized void onBandwidthUpdateRequested() {
185         if (mDataConnection == null) {
186             loge("onBandwidthUpdateRequested called on no-owner DcNetworkAgent!");
187             return;
188         }
189 
190         if (mPhone.getLceStatus() == RILConstants.LCE_ACTIVE     // active LCE service
191                 && mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
192             mPhone.mCi.pullLceData(mDataConnection.obtainMessage(
193                     DataConnection.EVENT_BW_REFRESH_RESPONSE));
194         }
195     }
196 
197     @Override
onValidationStatus(int status, Uri redirectUri)198     public synchronized void onValidationStatus(int status, Uri redirectUri) {
199         if (mDataConnection == null) {
200             loge("onValidationStatus called on no-owner DcNetworkAgent!");
201             return;
202         }
203 
204         logd("validation status: " + status + " with redirection URL: " + redirectUri);
205         DcTracker dct = mPhone.getDcTracker(mTransportType);
206         if (dct != null) {
207             Message msg = dct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
208                     status, mDataConnection.getCid(), redirectUri.toString());
209             msg.sendToTarget();
210         }
211     }
212 
isOwned(DataConnection dc, String reason)213     private synchronized boolean isOwned(DataConnection dc, String reason) {
214         if (mDataConnection == null) {
215             loge(reason + " called on no-owner DcNetworkAgent!");
216             return false;
217         } else if (mDataConnection != dc) {
218             loge(reason + ": This agent belongs to "
219                     + mDataConnection.getName() + ", ignored the request from " + dc.getName());
220             return false;
221         }
222         return true;
223     }
224 
225     /**
226      * Set the network capabilities.
227      *
228      * @param networkCapabilities The network capabilities.
229      * @param dc The data connection that invokes this method.
230      */
sendNetworkCapabilities(NetworkCapabilities networkCapabilities, DataConnection dc)231     public synchronized void sendNetworkCapabilities(NetworkCapabilities networkCapabilities,
232                                                      DataConnection dc) {
233         if (!isOwned(dc, "sendNetworkCapabilities")) return;
234 
235         if (!networkCapabilities.equals(mNetworkCapabilities)) {
236             String logStr = "Changed from " + mNetworkCapabilities + " to "
237                     + networkCapabilities + ", Data RAT="
238                     + mPhone.getServiceState().getRilDataRadioTechnology()
239                     + ", dc=" + mDataConnection.getName();
240             logd(logStr);
241             mNetCapsLocalLog.log(logStr);
242             if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
243                 // only log metrics for DataConnection with NET_CAPABILITY_INTERNET
244                 if (mNetworkCapabilities == null
245                         || networkCapabilities.hasCapability(
246                                 NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)
247                         != mNetworkCapabilities.hasCapability(
248                                 NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)) {
249                     TelephonyMetrics.getInstance().writeNetworkCapabilitiesChangedEvent(
250                             mPhone.getPhoneId(), networkCapabilities);
251                 }
252             }
253             mNetworkCapabilities = networkCapabilities;
254         }
255         sendNetworkCapabilities(networkCapabilities);
256     }
257 
258     /**
259      * Set the link properties
260      *
261      * @param linkProperties The link properties
262      * @param dc The data connection that invokes this method.
263      */
sendLinkProperties(@onNull LinkProperties linkProperties, DataConnection dc)264     public synchronized void sendLinkProperties(@NonNull LinkProperties linkProperties,
265                                                 DataConnection dc) {
266         if (!isOwned(dc, "sendLinkProperties")) return;
267 
268         sInterfaceNames.put(mId, dc.getLinkProperties().getInterfaceName());
269         sendLinkProperties(linkProperties);
270     }
271 
272     /**
273      * Set the network score.
274      *
275      * @param score The network score.
276      * @param dc The data connection that invokes this method.
277      */
sendNetworkScore(int score, DataConnection dc)278     public synchronized void sendNetworkScore(int score, DataConnection dc) {
279         if (!isOwned(dc, "sendNetworkScore")) return;
280         sendNetworkScore(score);
281     }
282 
283     /**
284      * Unregister the network agent from connectivity service.
285      *
286      * @param dc The data connection that invokes this method.
287      */
unregister(DataConnection dc)288     public synchronized void unregister(DataConnection dc) {
289         if (!isOwned(dc, "unregister")) return;
290 
291         logd("Unregister from connectivity service. " + sInterfaceNames.get(mId) + " removed.");
292         sInterfaceNames.remove(mId);
293         super.unregister();
294     }
295 
296     /**
297      * Set the network info.
298      *
299      * @param networkInfo The network info.
300      * @param dc The data connection that invokes this method.
301      */
sendNetworkInfo(NetworkInfo networkInfo, DataConnection dc)302     public synchronized void sendNetworkInfo(NetworkInfo networkInfo, DataConnection dc) {
303         if (!isOwned(dc, "sendNetworkInfo")) return;
304         final NetworkInfo.State oldState = mNetworkInfo.getState();
305         final NetworkInfo.State state = networkInfo.getState();
306         if (mNetworkInfo.getExtraInfo() != networkInfo.getExtraInfo()) {
307             setLegacyExtraInfo(networkInfo.getExtraInfo());
308         }
309         int subType = networkInfo.getSubtype();
310         if (mNetworkInfo.getSubtype() != subType) {
311             setLegacySubtype(subType, TelephonyManager.getNetworkTypeName(subType));
312         }
313         if ((oldState == NetworkInfo.State.SUSPENDED || oldState == NetworkInfo.State.CONNECTED)
314                 && state == NetworkInfo.State.DISCONNECTED) {
315             unregister(dc);
316         }
317         mNetworkInfo = new NetworkInfo(networkInfo);
318     }
319 
320     /**
321      * Get the latest sent network info.
322      *
323      * @return network info
324      */
getNetworkInfo()325     public synchronized NetworkInfo getNetworkInfo() {
326         return mNetworkInfo;
327     }
328 
329     @Override
onStartSocketKeepalive(int slot, @NonNull Duration interval, @NonNull KeepalivePacketData packet)330     public synchronized void onStartSocketKeepalive(int slot, @NonNull Duration interval,
331             @NonNull KeepalivePacketData packet) {
332         if (mDataConnection == null) {
333             loge("onStartSocketKeepalive called on no-owner DcNetworkAgent!");
334             return;
335         }
336 
337         if (packet instanceof NattKeepalivePacketData) {
338             mDataConnection.obtainMessage(DataConnection.EVENT_KEEPALIVE_START_REQUEST,
339                     slot, (int) interval.getSeconds(), packet).sendToTarget();
340         } else {
341             sendSocketKeepaliveEvent(slot, SocketKeepalive.ERROR_UNSUPPORTED);
342         }
343     }
344 
345     @Override
onStopSocketKeepalive(int slot)346     public synchronized void onStopSocketKeepalive(int slot) {
347         if (mDataConnection == null) {
348             loge("onStopSocketKeepalive called on no-owner DcNetworkAgent!");
349             return;
350         }
351 
352         mDataConnection.obtainMessage(DataConnection.EVENT_KEEPALIVE_STOP_REQUEST, slot)
353                 .sendToTarget();
354     }
355 
356     @Override
toString()357     public String toString() {
358         return "DcNetworkAgent-"
359                 + mId
360                 + " mDataConnection="
361                 + ((mDataConnection != null) ? mDataConnection.getName() : null)
362                 + " mTransportType="
363                 + AccessNetworkConstants.transportTypeToString(mTransportType)
364                 + " " + ((mDataConnection != null) ? mDataConnection.getLinkProperties() : null)
365                 + " mNetworkCapabilities=" + mNetworkCapabilities;
366     }
367 
368     /**
369      * Dump the state of transport manager
370      *
371      * @param fd File descriptor
372      * @param printWriter Print writer
373      * @param args Arguments
374      */
dump(FileDescriptor fd, PrintWriter printWriter, String[] args)375     public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
376         IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
377         pw.println(toString());
378         pw.increaseIndent();
379         pw.println("Net caps logs:");
380         mNetCapsLocalLog.dump(fd, pw, args);
381         pw.decreaseIndent();
382     }
383 
384     /**
385      * Log with debug level
386      *
387      * @param s is string log
388      */
logd(String s)389     private void logd(String s) {
390         Rlog.d(mTag, s);
391     }
392 
393     /**
394      * Log with error level
395      *
396      * @param s is string log
397      */
loge(String s)398     private void loge(String s) {
399         Rlog.e(mTag, s);
400     }
401 
402     class DcKeepaliveTracker {
403         private class KeepaliveRecord {
404             public int slotId;
405             public int currentStatus;
406 
KeepaliveRecord(int slotId, int status)407             KeepaliveRecord(int slotId, int status) {
408                 this.slotId = slotId;
409                 this.currentStatus = status;
410             }
411         }
412 
413         private final SparseArray<KeepaliveRecord> mKeepalives = new SparseArray();
414 
getHandleForSlot(int slotId)415         int getHandleForSlot(int slotId) {
416             for (int i = 0; i < mKeepalives.size(); i++) {
417                 KeepaliveRecord kr = mKeepalives.valueAt(i);
418                 if (kr.slotId == slotId) return mKeepalives.keyAt(i);
419             }
420             return -1;
421         }
422 
keepaliveStatusErrorToPacketKeepaliveError(int error)423         int keepaliveStatusErrorToPacketKeepaliveError(int error) {
424             switch(error) {
425                 case KeepaliveStatus.ERROR_NONE:
426                     return SocketKeepalive.SUCCESS;
427                 case KeepaliveStatus.ERROR_UNSUPPORTED:
428                     return SocketKeepalive.ERROR_UNSUPPORTED;
429                 case KeepaliveStatus.ERROR_NO_RESOURCES:
430                     return SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES;
431                 case KeepaliveStatus.ERROR_UNKNOWN:
432                 default:
433                     return SocketKeepalive.ERROR_HARDWARE_ERROR;
434             }
435         }
436 
handleKeepaliveStarted(final int slot, KeepaliveStatus ks)437         void handleKeepaliveStarted(final int slot, KeepaliveStatus ks) {
438             switch (ks.statusCode) {
439                 case KeepaliveStatus.STATUS_INACTIVE:
440                     DcNetworkAgent.this.sendSocketKeepaliveEvent(slot,
441                             keepaliveStatusErrorToPacketKeepaliveError(ks.errorCode));
442                     break;
443                 case KeepaliveStatus.STATUS_ACTIVE:
444                     DcNetworkAgent.this.sendSocketKeepaliveEvent(
445                             slot, SocketKeepalive.SUCCESS);
446                     // fall through to add record
447                 case KeepaliveStatus.STATUS_PENDING:
448                     logd("Adding keepalive handle="
449                             + ks.sessionHandle + " slot = " + slot);
450                     mKeepalives.put(ks.sessionHandle,
451                             new KeepaliveRecord(
452                                     slot, ks.statusCode));
453                     break;
454                 default:
455                     logd("Invalid KeepaliveStatus Code: " + ks.statusCode);
456                     break;
457             }
458         }
459 
handleKeepaliveStatus(KeepaliveStatus ks)460         void handleKeepaliveStatus(KeepaliveStatus ks) {
461             final KeepaliveRecord kr;
462             kr = mKeepalives.get(ks.sessionHandle);
463 
464             if (kr == null) {
465                 // If there is no slot for the session handle, we received an event
466                 // for a different data connection. This is not an error because the
467                 // keepalive session events are broadcast to all listeners.
468                 loge("Discarding keepalive event for different data connection:" + ks);
469                 return;
470             }
471             // Switch on the current state, to see what we do with the status update
472             switch (kr.currentStatus) {
473                 case KeepaliveStatus.STATUS_INACTIVE:
474                     logd("Inactive Keepalive received status!");
475                     DcNetworkAgent.this.sendSocketKeepaliveEvent(
476                             kr.slotId, SocketKeepalive.ERROR_HARDWARE_ERROR);
477                     break;
478                 case KeepaliveStatus.STATUS_PENDING:
479                     switch (ks.statusCode) {
480                         case KeepaliveStatus.STATUS_INACTIVE:
481                             DcNetworkAgent.this.sendSocketKeepaliveEvent(kr.slotId,
482                                     keepaliveStatusErrorToPacketKeepaliveError(ks.errorCode));
483                             kr.currentStatus = KeepaliveStatus.STATUS_INACTIVE;
484                             mKeepalives.remove(ks.sessionHandle);
485                             break;
486                         case KeepaliveStatus.STATUS_ACTIVE:
487                             logd("Pending Keepalive received active status!");
488                             kr.currentStatus = KeepaliveStatus.STATUS_ACTIVE;
489                             DcNetworkAgent.this.sendSocketKeepaliveEvent(
490                                     kr.slotId, SocketKeepalive.SUCCESS);
491                             break;
492                         case KeepaliveStatus.STATUS_PENDING:
493                             loge("Invalid unsolicied Keepalive Pending Status!");
494                             break;
495                         default:
496                             loge("Invalid Keepalive Status received, " + ks.statusCode);
497                     }
498                     break;
499                 case KeepaliveStatus.STATUS_ACTIVE:
500                     switch (ks.statusCode) {
501                         case KeepaliveStatus.STATUS_INACTIVE:
502                             logd("Keepalive received stopped status!");
503                             DcNetworkAgent.this.sendSocketKeepaliveEvent(
504                                     kr.slotId, SocketKeepalive.SUCCESS);
505 
506                             kr.currentStatus = KeepaliveStatus.STATUS_INACTIVE;
507                             mKeepalives.remove(ks.sessionHandle);
508                             break;
509                         case KeepaliveStatus.STATUS_PENDING:
510                         case KeepaliveStatus.STATUS_ACTIVE:
511                             loge("Active Keepalive received invalid status!");
512                             break;
513                         default:
514                             loge("Invalid Keepalive Status received, " + ks.statusCode);
515                     }
516                     break;
517                 default:
518                     loge("Invalid Keepalive Status received, " + kr.currentStatus);
519             }
520         }
521     }
522 }
523