1 /*
2  * Copyright (C) 2022 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.networkstack.tethering;
18 
19 import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_0;
20 import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_1;
21 import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_NONE;
22 import static com.android.networkstack.tethering.OffloadHardwareInterface.halVerToString;
23 import static com.android.networkstack.tethering.util.TetheringUtils.uint16;
24 
25 import android.annotation.NonNull;
26 import android.hardware.tetheroffload.config.V1_0.IOffloadConfig;
27 import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
28 import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
29 import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
30 import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
31 import android.hardware.tetheroffload.control.V1_1.ITetheringOffloadCallback;
32 import android.os.Handler;
33 import android.os.NativeHandle;
34 import android.os.RemoteException;
35 import android.system.OsConstants;
36 import android.util.Log;
37 
38 import com.android.net.module.util.SharedLog;
39 import com.android.networkstack.tethering.OffloadHardwareInterface.ForwardedStats;
40 import com.android.networkstack.tethering.OffloadHardwareInterface.OffloadHalCallback;
41 
42 import java.util.ArrayList;
43 import java.util.NoSuchElementException;
44 
45 /**
46  * The implementation of IOffloadHal which based on HIDL interfaces
47  */
48 public class OffloadHalHidlImpl implements IOffloadHal {
49     private static final String TAG = OffloadHalHidlImpl.class.getSimpleName();
50     private static final String YIELDS = " -> ";
51 
52     private final Handler mHandler;
53     private final SharedLog mLog;
54     private final IOffloadConfig mIOffloadConfig;
55     private final IOffloadControl mIOffloadControl;
56     @OffloadHardwareInterface.OffloadHalVersion
57     private final int mOffloadControlVersion;
58 
59     private OffloadHalCallback mOffloadHalCallback;
60     private TetheringOffloadCallback mTetheringOffloadCallback;
61 
OffloadHalHidlImpl(int version, @NonNull IOffloadConfig config, @NonNull IOffloadControl control, @NonNull Handler handler, @NonNull SharedLog log)62     public OffloadHalHidlImpl(int version, @NonNull IOffloadConfig config,
63             @NonNull IOffloadControl control, @NonNull Handler handler, @NonNull SharedLog log) {
64         mOffloadControlVersion = version;
65         mIOffloadConfig = config;
66         mIOffloadControl = control;
67         mHandler = handler;
68         mLog = log.forSubComponent(TAG);
69     }
70 
71     /**
72      * Initialize the Tetheroffload HAL. Provides bound netlink file descriptors for use in the
73      * management process.
74      */
initOffload(@onNull NativeHandle handle1, @NonNull NativeHandle handle2, @NonNull OffloadHalCallback callback)75     public boolean initOffload(@NonNull NativeHandle handle1, @NonNull NativeHandle handle2,
76             @NonNull OffloadHalCallback callback) {
77         final String logmsg = "initOffload()";
78 
79         mOffloadHalCallback = callback;
80         mTetheringOffloadCallback = new TetheringOffloadCallback(
81                 mHandler, mOffloadHalCallback, mLog, mOffloadControlVersion);
82         final CbResults results = new CbResults();
83         try {
84             mIOffloadConfig.setHandles(handle1, handle2,
85                     (boolean success, String errMsg) -> {
86                         results.mSuccess = success;
87                         results.mErrMsg = errMsg;
88                     });
89             mIOffloadControl.initOffload(
90                     mTetheringOffloadCallback,
91                     (boolean success, String errMsg) -> {
92                         results.mSuccess = success;
93                         results.mErrMsg = errMsg;
94                     });
95         } catch (RemoteException e) {
96             record(logmsg, e);
97             return false;
98         }
99 
100         record(logmsg, results);
101         return results.mSuccess;
102     }
103 
104     /** Stop the Tetheroffload HAL. */
stopOffload()105     public boolean stopOffload() {
106         try {
107             mIOffloadControl.stopOffload(
108                     (boolean success, String errMsg) -> {
109                         if (!success) mLog.e("stopOffload failed: " + errMsg);
110                     });
111         } catch (RemoteException e) {
112             mLog.e("failed to stopOffload: " + e);
113         }
114         mOffloadHalCallback = null;
115         mTetheringOffloadCallback = null;
116         mLog.log("stopOffload()");
117         return true;
118     }
119 
120     /** Get HAL interface version number. */
getVersion()121     public int getVersion() {
122         return mOffloadControlVersion;
123     }
124 
125     /** Get Tx/Rx usage from last query. */
getForwardedStats(@onNull String upstream)126     public ForwardedStats getForwardedStats(@NonNull String upstream) {
127         final String logmsg = String.format("getForwardedStats(%s)",  upstream);
128 
129         final ForwardedStats stats = new ForwardedStats();
130         try {
131             mIOffloadControl.getForwardedStats(
132                     upstream,
133                     (long rxBytes, long txBytes) -> {
134                         stats.rxBytes = (rxBytes > 0) ? rxBytes : 0;
135                         stats.txBytes = (txBytes > 0) ? txBytes : 0;
136                     });
137         } catch (RemoteException e) {
138             record(logmsg, e);
139             return stats;
140         }
141 
142         return stats;
143     }
144 
145     /** Set local prefixes to offload management process. */
setLocalPrefixes(@onNull ArrayList<String> localPrefixes)146     public boolean setLocalPrefixes(@NonNull ArrayList<String> localPrefixes) {
147         final String logmsg = String.format("setLocalPrefixes([%s])",
148                 String.join(",", localPrefixes));
149 
150         final CbResults results = new CbResults();
151         try {
152             mIOffloadControl.setLocalPrefixes(localPrefixes,
153                     (boolean success, String errMsg) -> {
154                         results.mSuccess = success;
155                         results.mErrMsg = errMsg;
156                     });
157         } catch (RemoteException e) {
158             record(logmsg, e);
159             return false;
160         }
161 
162         record(logmsg, results);
163         return results.mSuccess;
164     }
165 
166     /** Set data limit value to offload management process. */
setDataLimit(@onNull String iface, long limit)167     public boolean setDataLimit(@NonNull String iface, long limit) {
168 
169         final String logmsg = String.format("setDataLimit(%s, %d)", iface, limit);
170 
171         final CbResults results = new CbResults();
172         try {
173             mIOffloadControl.setDataLimit(
174                     iface, limit,
175                     (boolean success, String errMsg) -> {
176                         results.mSuccess = success;
177                         results.mErrMsg = errMsg;
178                     });
179         } catch (RemoteException e) {
180             record(logmsg, e);
181             return false;
182         }
183 
184         record(logmsg, results);
185         return results.mSuccess;
186     }
187 
188     /** Set data warning and limit value to offload management process. */
setDataWarningAndLimit(@onNull String iface, long warning, long limit)189     public boolean setDataWarningAndLimit(@NonNull String iface, long warning, long limit) {
190         if (mOffloadControlVersion < OFFLOAD_HAL_VERSION_HIDL_1_1) {
191             throw new UnsupportedOperationException(
192                     "setDataWarningAndLimit is not supported below HAL V1.1");
193         }
194         final String logmsg =
195                 String.format("setDataWarningAndLimit(%s, %d, %d)", iface, warning, limit);
196 
197         final CbResults results = new CbResults();
198         try {
199             ((android.hardware.tetheroffload.control.V1_1.IOffloadControl) mIOffloadControl)
200                     .setDataWarningAndLimit(
201                             iface, warning, limit,
202                             (boolean success, String errMsg) -> {
203                                 results.mSuccess = success;
204                                 results.mErrMsg = errMsg;
205                             });
206         } catch (RemoteException e) {
207             record(logmsg, e);
208             return false;
209         }
210 
211         record(logmsg, results);
212         return results.mSuccess;
213     }
214 
215     /** Set upstream parameters to offload management process. */
setUpstreamParameters(@onNull String iface, @NonNull String v4addr, @NonNull String v4gateway, @NonNull ArrayList<String> v6gws)216     public boolean setUpstreamParameters(@NonNull String iface, @NonNull String v4addr,
217             @NonNull String v4gateway, @NonNull ArrayList<String> v6gws) {
218         final String logmsg = String.format("setUpstreamParameters(%s, %s, %s, [%s])",
219                 iface, v4addr, v4gateway, String.join(",", v6gws));
220 
221         final CbResults results = new CbResults();
222         try {
223             mIOffloadControl.setUpstreamParameters(
224                     iface, v4addr, v4gateway, v6gws,
225                     (boolean success, String errMsg) -> {
226                         results.mSuccess = success;
227                         results.mErrMsg = errMsg;
228                     });
229         } catch (RemoteException e) {
230             record(logmsg, e);
231             return false;
232         }
233 
234         record(logmsg, results);
235         return results.mSuccess;
236     }
237 
238     /** Add downstream prefix to offload management process. */
addDownstream(@onNull String ifname, @NonNull String prefix)239     public boolean addDownstream(@NonNull String ifname, @NonNull String prefix) {
240         final String logmsg = String.format("addDownstream(%s, %s)", ifname, prefix);
241 
242         final CbResults results = new CbResults();
243         try {
244             mIOffloadControl.addDownstream(ifname, prefix,
245                     (boolean success, String errMsg) -> {
246                         results.mSuccess = success;
247                         results.mErrMsg = errMsg;
248                     });
249         } catch (RemoteException e) {
250             record(logmsg, e);
251             return false;
252         }
253 
254         record(logmsg, results);
255         return results.mSuccess;
256     }
257 
258     /** Remove downstream prefix from offload management process. */
removeDownstream(@onNull String ifname, @NonNull String prefix)259     public boolean removeDownstream(@NonNull String ifname, @NonNull String prefix) {
260         final String logmsg = String.format("removeDownstream(%s, %s)", ifname, prefix);
261 
262         final CbResults results = new CbResults();
263         try {
264             mIOffloadControl.removeDownstream(ifname, prefix,
265                     (boolean success, String errMsg) -> {
266                         results.mSuccess = success;
267                         results.mErrMsg = errMsg;
268                     });
269         } catch (RemoteException e) {
270             record(logmsg, e);
271             return false;
272         }
273 
274         record(logmsg, results);
275         return results.mSuccess;
276     }
277 
278     /**
279      * Get {@link IOffloadHal} object from the HIDL service.
280      *
281      * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
282      * @param log Log to be used by the repository.
283      */
getIOffloadHal(Handler handler, SharedLog log)284     public static IOffloadHal getIOffloadHal(Handler handler, SharedLog log) {
285         IOffloadConfig config = null;
286         try {
287             config = IOffloadConfig.getService(true /*retry*/);
288         } catch (RemoteException e) {
289             log.e("getIOffloadConfig error " + e);
290             return null;
291         } catch (NoSuchElementException e) {
292             log.i("getIOffloadConfig Tether Offload HAL not present/implemented");
293             return null;
294         }
295 
296         IOffloadControl control = null;
297         int version = OFFLOAD_HAL_VERSION_NONE;
298         try {
299             control = android.hardware.tetheroffload.control
300                     .V1_1.IOffloadControl.getService(true /*retry*/);
301             version = OFFLOAD_HAL_VERSION_HIDL_1_1;
302         } catch (NoSuchElementException e) {
303             // Unsupported by device.
304         } catch (RemoteException e) {
305             log.e("Unable to get offload control " + OFFLOAD_HAL_VERSION_HIDL_1_1);
306         }
307         if (control == null) {
308             try {
309                 control = IOffloadControl.getService(true /*retry*/);
310                 version = OFFLOAD_HAL_VERSION_HIDL_1_0;
311             } catch (NoSuchElementException e) {
312                 // Unsupported by device.
313             } catch (RemoteException e) {
314                 log.e("Unable to get offload control " + OFFLOAD_HAL_VERSION_HIDL_1_0);
315             }
316         }
317 
318         if (config == null || control == null) return null;
319 
320         return new OffloadHalHidlImpl(version, config, control, handler, log);
321     }
322 
record(String msg, Throwable t)323     private void record(String msg, Throwable t) {
324         mLog.e(msg + YIELDS + "exception: " + t);
325     }
326 
record(String msg, CbResults results)327     private void record(String msg, CbResults results) {
328         final String logmsg = msg + YIELDS + results;
329         if (!results.mSuccess) {
330             mLog.e(logmsg);
331         } else {
332             mLog.log(logmsg);
333         }
334     }
335 
336     private static class TetheringOffloadCallback extends ITetheringOffloadCallback.Stub {
337         public final Handler handler;
338         public final OffloadHalCallback callback;
339         public final SharedLog log;
340         private final int mOffloadControlVersion;
341 
TetheringOffloadCallback( Handler h, OffloadHalCallback cb, SharedLog sharedLog, int offloadControlVersion)342         TetheringOffloadCallback(
343                 Handler h, OffloadHalCallback cb, SharedLog sharedLog, int offloadControlVersion) {
344             handler = h;
345             callback = cb;
346             log = sharedLog;
347             this.mOffloadControlVersion = offloadControlVersion;
348         }
349 
handleOnEvent(int event)350         private void handleOnEvent(int event) {
351             switch (event) {
352                 case OffloadCallbackEvent.OFFLOAD_STARTED:
353                     callback.onStarted();
354                     break;
355                 case OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR:
356                     callback.onStoppedError();
357                     break;
358                 case OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED:
359                     callback.onStoppedUnsupported();
360                     break;
361                 case OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE:
362                     callback.onSupportAvailable();
363                     break;
364                 case OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED:
365                     callback.onStoppedLimitReached();
366                     break;
367                 case android.hardware.tetheroffload.control
368                         .V1_1.OffloadCallbackEvent.OFFLOAD_WARNING_REACHED:
369                     callback.onWarningReached();
370                     break;
371                 default:
372                     log.e("Unsupported OffloadCallbackEvent: " + event);
373             }
374         }
375 
376         @Override
onEvent(int event)377         public void onEvent(int event) {
378             // The implementation should never call onEvent()) if the event is already reported
379             // through newer callback.
380             if (mOffloadControlVersion > OFFLOAD_HAL_VERSION_HIDL_1_0) {
381                 Log.wtf(TAG, "onEvent(" + event + ") fired on HAL "
382                         + halVerToString(mOffloadControlVersion));
383             }
384             handler.post(() -> {
385                 handleOnEvent(event);
386             });
387         }
388 
389         @Override
onEvent_1_1(int event)390         public void onEvent_1_1(int event) {
391             if (mOffloadControlVersion < OFFLOAD_HAL_VERSION_HIDL_1_1) {
392                 Log.wtf(TAG, "onEvent_1_1(" + event + ") fired on HAL "
393                         + halVerToString(mOffloadControlVersion));
394                 return;
395             }
396             handler.post(() -> {
397                 handleOnEvent(event);
398             });
399         }
400 
401         @Override
updateTimeout(NatTimeoutUpdate params)402         public void updateTimeout(NatTimeoutUpdate params) {
403             handler.post(() -> {
404                 callback.onNatTimeoutUpdate(
405                         networkProtocolToOsConstant(params.proto),
406                         params.src.addr, uint16(params.src.port),
407                         params.dst.addr, uint16(params.dst.port));
408             });
409         }
410     }
411 
networkProtocolToOsConstant(int proto)412     private static int networkProtocolToOsConstant(int proto) {
413         switch (proto) {
414             case NetworkProtocol.TCP: return OsConstants.IPPROTO_TCP;
415             case NetworkProtocol.UDP: return OsConstants.IPPROTO_UDP;
416             default:
417                 // The caller checks this value and will log an error. Just make
418                 // sure it won't collide with valid OsConstants.IPPROTO_* values.
419                 return -Math.abs(proto);
420         }
421     }
422 
423     private static class CbResults {
424         boolean mSuccess;
425         String mErrMsg;
426 
427         @Override
toString()428         public String toString() {
429             if (mSuccess) {
430                 return "ok";
431             } else {
432                 return "fail: " + mErrMsg;
433             }
434         }
435     }
436 }
437