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_AIDL;
20 
21 import android.annotation.NonNull;
22 import android.annotation.TargetApi;
23 import android.hardware.tetheroffload.ForwardedStats;
24 import android.hardware.tetheroffload.IOffload;
25 import android.hardware.tetheroffload.ITetheringOffloadCallback;
26 import android.hardware.tetheroffload.NatTimeoutUpdate;
27 import android.hardware.tetheroffload.NetworkProtocol;
28 import android.hardware.tetheroffload.OffloadCallbackEvent;
29 import android.os.Build;
30 import android.os.Handler;
31 import android.os.NativeHandle;
32 import android.os.ParcelFileDescriptor;
33 import android.os.ServiceManager;
34 import android.system.OsConstants;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.modules.utils.build.SdkLevel;
38 import com.android.net.module.util.SharedLog;
39 import com.android.networkstack.tethering.OffloadHardwareInterface.OffloadHalCallback;
40 
41 import java.util.ArrayList;
42 
43 /**
44  * The implementation of IOffloadHal which based on Stable AIDL interface
45  */
46 @TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
47 public class OffloadHalAidlImpl implements IOffloadHal {
48     private static final String TAG = OffloadHalAidlImpl.class.getSimpleName();
49     private static final String HAL_INSTANCE_NAME = IOffload.DESCRIPTOR + "/default";
50 
51     private final Handler mHandler;
52     private final SharedLog mLog;
53     private final IOffload mIOffload;
54     @OffloadHardwareInterface.OffloadHalVersion
55     private final int mOffloadVersion;
56 
57     private TetheringOffloadCallback mTetheringOffloadCallback;
58 
59     @VisibleForTesting
OffloadHalAidlImpl(int version, @NonNull IOffload offload, @NonNull Handler handler, @NonNull SharedLog log)60     public OffloadHalAidlImpl(int version, @NonNull IOffload offload, @NonNull Handler handler,
61             @NonNull SharedLog log) {
62         mOffloadVersion = version;
63         mIOffload = offload;
64         mHandler = handler;
65         mLog = log.forSubComponent(TAG);
66     }
67 
68     /**
69      * Initialize the Tetheroffload HAL. Provides bound netlink file descriptors for use in the
70      * management process.
71      */
initOffload(@onNull NativeHandle handle1, @NonNull NativeHandle handle2, @NonNull OffloadHalCallback callback)72     public boolean initOffload(@NonNull NativeHandle handle1, @NonNull NativeHandle handle2,
73             @NonNull OffloadHalCallback callback) {
74         final String methodStr = String.format("initOffload(%d, %d, %s)",
75                 handle1.getFileDescriptor().getInt$(), handle2.getFileDescriptor().getInt$(),
76                 (callback == null) ? "null"
77                 : "0x" + Integer.toHexString(System.identityHashCode(callback)));
78         mTetheringOffloadCallback = new TetheringOffloadCallback(mHandler, callback, mLog);
79         try {
80             mIOffload.initOffload(
81                     ParcelFileDescriptor.adoptFd(handle1.getFileDescriptor().getInt$()),
82                     ParcelFileDescriptor.adoptFd(handle2.getFileDescriptor().getInt$()),
83                     mTetheringOffloadCallback);
84         } catch (Exception e) {
85             logAndIgnoreException(e, methodStr);
86             return false;
87         }
88         mLog.i(methodStr);
89         return true;
90     }
91 
92     /** Stop the Tetheroffload HAL. */
stopOffload()93     public boolean stopOffload() {
94         final String methodStr = "stopOffload()";
95         try {
96             mIOffload.stopOffload();
97         } catch (Exception e) {
98             logAndIgnoreException(e, methodStr);
99             return false;
100         }
101 
102         mTetheringOffloadCallback = null;
103         mLog.i(methodStr);
104         return true;
105     }
106 
107     /** Get HAL interface version number. */
getVersion()108     public int getVersion() {
109         return mOffloadVersion;
110     }
111 
112     /** Get Tx/Rx usage from last query. */
getForwardedStats(@onNull String upstream)113     public OffloadHardwareInterface.ForwardedStats getForwardedStats(@NonNull String upstream) {
114         ForwardedStats stats = new ForwardedStats();
115         final String methodStr = String.format("getForwardedStats(%s)",  upstream);
116         try {
117             stats = mIOffload.getForwardedStats(upstream);
118         } catch (Exception e) {
119             logAndIgnoreException(e, methodStr);
120         }
121         mLog.i(methodStr);
122         return new OffloadHardwareInterface.ForwardedStats(stats.rxBytes, stats.txBytes);
123     }
124 
125     /** Set local prefixes to offload management process. */
setLocalPrefixes(@onNull ArrayList<String> localPrefixes)126     public boolean setLocalPrefixes(@NonNull ArrayList<String> localPrefixes) {
127         final String methodStr = String.format("setLocalPrefixes([%s])",
128                 String.join(",", localPrefixes));
129         try {
130             mIOffload.setLocalPrefixes(localPrefixes.toArray(new String[localPrefixes.size()]));
131         } catch (Exception e) {
132             logAndIgnoreException(e, methodStr);
133             return false;
134         }
135         mLog.i(methodStr);
136         return true;
137     }
138 
139     /**
140      * Set data limit value to offload management process.
141      * Method setDataLimit is deprecated in AIDL, so call setDataWarningAndLimit instead,
142      * with warningBytes set to its MAX_VALUE.
143      */
setDataLimit(@onNull String iface, long limit)144     public boolean setDataLimit(@NonNull String iface, long limit) {
145         final long warning = Long.MAX_VALUE;
146         final String methodStr = String.format("setDataLimit(%s, %d)", iface, limit);
147         try {
148             mIOffload.setDataWarningAndLimit(iface, warning, limit);
149         } catch (Exception e) {
150             logAndIgnoreException(e, methodStr);
151             return false;
152         }
153         mLog.i(methodStr);
154         return true;
155     }
156 
157     /** Set data warning and limit value to offload management process. */
setDataWarningAndLimit(@onNull String iface, long warning, long limit)158     public boolean setDataWarningAndLimit(@NonNull String iface, long warning, long limit) {
159         final String methodStr =
160                 String.format("setDataWarningAndLimit(%s, %d, %d)", iface, warning, limit);
161         try {
162             mIOffload.setDataWarningAndLimit(iface, warning, limit);
163         } catch (Exception e) {
164             logAndIgnoreException(e, methodStr);
165             return false;
166         }
167         mLog.i(methodStr);
168         return true;
169     }
170 
171     /** Set upstream parameters to offload management process. */
setUpstreamParameters(@onNull String iface, @NonNull String v4addr, @NonNull String v4gateway, @NonNull ArrayList<String> v6gws)172     public boolean setUpstreamParameters(@NonNull String iface, @NonNull String v4addr,
173             @NonNull String v4gateway, @NonNull ArrayList<String> v6gws) {
174         final String methodStr = String.format("setUpstreamParameters(%s, %s, %s, [%s])",
175                 iface, v4addr, v4gateway, String.join(",", v6gws));
176         try {
177             mIOffload.setUpstreamParameters(iface, v4addr, v4gateway,
178                     v6gws.toArray(new String[v6gws.size()]));
179         } catch (Exception e) {
180             logAndIgnoreException(e, methodStr);
181             return false;
182         }
183         mLog.i(methodStr);
184         return true;
185     }
186 
187     /** Add downstream prefix to offload management process. */
addDownstream(@onNull String ifname, @NonNull String prefix)188     public boolean addDownstream(@NonNull String ifname, @NonNull String prefix) {
189         final String methodStr = String.format("addDownstream(%s, %s)", ifname, prefix);
190         try {
191             mIOffload.addDownstream(ifname, prefix);
192         } catch (Exception e) {
193             logAndIgnoreException(e, methodStr);
194             return false;
195         }
196         mLog.i(methodStr);
197         return true;
198     }
199 
200     /** Remove downstream prefix from offload management process. */
removeDownstream(@onNull String ifname, @NonNull String prefix)201     public boolean removeDownstream(@NonNull String ifname, @NonNull String prefix) {
202         final String methodStr = String.format("removeDownstream(%s, %s)", ifname, prefix);
203         try {
204             mIOffload.removeDownstream(ifname, prefix);
205         } catch (Exception e) {
206             logAndIgnoreException(e, methodStr);
207             return false;
208         }
209         mLog.i(methodStr);
210         return true;
211     }
212 
213     /**
214      * Get {@link IOffloadHal} object from the AIDL service.
215      *
216      * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
217      * @param log Log to be used by the repository.
218      */
getIOffloadHal(Handler handler, SharedLog log)219     public static IOffloadHal getIOffloadHal(Handler handler, SharedLog log) {
220         // Tetheroffload AIDL interface is only supported after U.
221         if (!SdkLevel.isAtLeastU() || !ServiceManager.isDeclared(HAL_INSTANCE_NAME)) return null;
222 
223         IOffload offload = IOffload.Stub.asInterface(
224                 ServiceManager.waitForDeclaredService(HAL_INSTANCE_NAME));
225         if (offload == null) return null;
226 
227         return new OffloadHalAidlImpl(OFFLOAD_HAL_VERSION_AIDL, offload, handler, log);
228     }
229 
logAndIgnoreException(Exception e, final String methodStr)230     private void logAndIgnoreException(Exception e, final String methodStr) {
231         mLog.e(methodStr + " failed with " + e.getClass().getSimpleName() + ": ", e);
232     }
233 
234     private static class TetheringOffloadCallback extends ITetheringOffloadCallback.Stub {
235         public final Handler handler;
236         public final OffloadHalCallback callback;
237         public final SharedLog log;
238 
TetheringOffloadCallback( Handler h, OffloadHalCallback cb, SharedLog sharedLog)239         TetheringOffloadCallback(
240                 Handler h, OffloadHalCallback cb, SharedLog sharedLog) {
241             handler = h;
242             callback = cb;
243             log = sharedLog;
244         }
245 
handleOnEvent(int event)246         private void handleOnEvent(int event) {
247             switch (event) {
248                 case OffloadCallbackEvent.OFFLOAD_STARTED:
249                     callback.onStarted();
250                     break;
251                 case OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR:
252                     callback.onStoppedError();
253                     break;
254                 case OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED:
255                     callback.onStoppedUnsupported();
256                     break;
257                 case OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE:
258                     callback.onSupportAvailable();
259                     break;
260                 case OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED:
261                     callback.onStoppedLimitReached();
262                     break;
263                 case OffloadCallbackEvent.OFFLOAD_WARNING_REACHED:
264                     callback.onWarningReached();
265                     break;
266                 default:
267                     log.e("Unsupported OffloadCallbackEvent: " + event);
268             }
269         }
270 
271         @Override
onEvent(int event)272         public void onEvent(int event) {
273             handler.post(() -> {
274                 handleOnEvent(event);
275             });
276         }
277 
278         @Override
updateTimeout(NatTimeoutUpdate params)279         public void updateTimeout(NatTimeoutUpdate params) {
280             handler.post(() -> {
281                 callback.onNatTimeoutUpdate(
282                         networkProtocolToOsConstant(params.proto),
283                         params.src.addr, params.src.port,
284                         params.dst.addr, params.dst.port);
285             });
286         }
287 
288         @Override
getInterfaceHash()289         public String getInterfaceHash() {
290             return ITetheringOffloadCallback.HASH;
291         }
292 
293         @Override
getInterfaceVersion()294         public int getInterfaceVersion() {
295             return ITetheringOffloadCallback.VERSION;
296         }
297     }
298 
networkProtocolToOsConstant(int proto)299     private static int networkProtocolToOsConstant(int proto) {
300         switch (proto) {
301             case NetworkProtocol.TCP: return OsConstants.IPPROTO_TCP;
302             case NetworkProtocol.UDP: return OsConstants.IPPROTO_UDP;
303             default:
304                 // The caller checks this value and will log an error. Just make
305                 // sure it won't collide with valid OsConstants.IPPROTO_* values.
306                 return -Math.abs(proto);
307         }
308     }
309 }
310