/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.networkstack.tethering; import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_AIDL; import android.annotation.NonNull; import android.annotation.TargetApi; import android.hardware.tetheroffload.ForwardedStats; import android.hardware.tetheroffload.IOffload; import android.hardware.tetheroffload.ITetheringOffloadCallback; import android.hardware.tetheroffload.NatTimeoutUpdate; import android.hardware.tetheroffload.NetworkProtocol; import android.hardware.tetheroffload.OffloadCallbackEvent; import android.os.Build; import android.os.Handler; import android.os.NativeHandle; import android.os.ParcelFileDescriptor; import android.os.ServiceManager; import android.system.OsConstants; import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.SharedLog; import com.android.networkstack.tethering.OffloadHardwareInterface.OffloadHalCallback; import java.util.ArrayList; /** * The implementation of IOffloadHal which based on Stable AIDL interface */ @TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public class OffloadHalAidlImpl implements IOffloadHal { private static final String TAG = OffloadHalAidlImpl.class.getSimpleName(); private static final String HAL_INSTANCE_NAME = IOffload.DESCRIPTOR + "/default"; private final Handler mHandler; private final SharedLog mLog; private final IOffload mIOffload; @OffloadHardwareInterface.OffloadHalVersion private final int mOffloadVersion; private TetheringOffloadCallback mTetheringOffloadCallback; @VisibleForTesting public OffloadHalAidlImpl(int version, @NonNull IOffload offload, @NonNull Handler handler, @NonNull SharedLog log) { mOffloadVersion = version; mIOffload = offload; mHandler = handler; mLog = log.forSubComponent(TAG); } /** * Initialize the Tetheroffload HAL. Provides bound netlink file descriptors for use in the * management process. */ public boolean initOffload(@NonNull NativeHandle handle1, @NonNull NativeHandle handle2, @NonNull OffloadHalCallback callback) { final String methodStr = String.format("initOffload(%d, %d, %s)", handle1.getFileDescriptor().getInt$(), handle2.getFileDescriptor().getInt$(), (callback == null) ? "null" : "0x" + Integer.toHexString(System.identityHashCode(callback))); mTetheringOffloadCallback = new TetheringOffloadCallback(mHandler, callback, mLog); try { mIOffload.initOffload( ParcelFileDescriptor.adoptFd(handle1.getFileDescriptor().getInt$()), ParcelFileDescriptor.adoptFd(handle2.getFileDescriptor().getInt$()), mTetheringOffloadCallback); } catch (Exception e) { logAndIgnoreException(e, methodStr); return false; } mLog.i(methodStr); return true; } /** Stop the Tetheroffload HAL. */ public boolean stopOffload() { final String methodStr = "stopOffload()"; try { mIOffload.stopOffload(); } catch (Exception e) { logAndIgnoreException(e, methodStr); return false; } mTetheringOffloadCallback = null; mLog.i(methodStr); return true; } /** Get HAL interface version number. */ public int getVersion() { return mOffloadVersion; } /** Get Tx/Rx usage from last query. */ public OffloadHardwareInterface.ForwardedStats getForwardedStats(@NonNull String upstream) { ForwardedStats stats = new ForwardedStats(); final String methodStr = String.format("getForwardedStats(%s)", upstream); try { stats = mIOffload.getForwardedStats(upstream); } catch (Exception e) { logAndIgnoreException(e, methodStr); } mLog.i(methodStr); return new OffloadHardwareInterface.ForwardedStats(stats.rxBytes, stats.txBytes); } /** Set local prefixes to offload management process. */ public boolean setLocalPrefixes(@NonNull ArrayList<String> localPrefixes) { final String methodStr = String.format("setLocalPrefixes([%s])", String.join(",", localPrefixes)); try { mIOffload.setLocalPrefixes(localPrefixes.toArray(new String[localPrefixes.size()])); } catch (Exception e) { logAndIgnoreException(e, methodStr); return false; } mLog.i(methodStr); return true; } /** * Set data limit value to offload management process. * Method setDataLimit is deprecated in AIDL, so call setDataWarningAndLimit instead, * with warningBytes set to its MAX_VALUE. */ public boolean setDataLimit(@NonNull String iface, long limit) { final long warning = Long.MAX_VALUE; final String methodStr = String.format("setDataLimit(%s, %d)", iface, limit); try { mIOffload.setDataWarningAndLimit(iface, warning, limit); } catch (Exception e) { logAndIgnoreException(e, methodStr); return false; } mLog.i(methodStr); return true; } /** Set data warning and limit value to offload management process. */ public boolean setDataWarningAndLimit(@NonNull String iface, long warning, long limit) { final String methodStr = String.format("setDataWarningAndLimit(%s, %d, %d)", iface, warning, limit); try { mIOffload.setDataWarningAndLimit(iface, warning, limit); } catch (Exception e) { logAndIgnoreException(e, methodStr); return false; } mLog.i(methodStr); return true; } /** Set upstream parameters to offload management process. */ public boolean setUpstreamParameters(@NonNull String iface, @NonNull String v4addr, @NonNull String v4gateway, @NonNull ArrayList<String> v6gws) { final String methodStr = String.format("setUpstreamParameters(%s, %s, %s, [%s])", iface, v4addr, v4gateway, String.join(",", v6gws)); try { mIOffload.setUpstreamParameters(iface, v4addr, v4gateway, v6gws.toArray(new String[v6gws.size()])); } catch (Exception e) { logAndIgnoreException(e, methodStr); return false; } mLog.i(methodStr); return true; } /** Add downstream prefix to offload management process. */ public boolean addDownstream(@NonNull String ifname, @NonNull String prefix) { final String methodStr = String.format("addDownstream(%s, %s)", ifname, prefix); try { mIOffload.addDownstream(ifname, prefix); } catch (Exception e) { logAndIgnoreException(e, methodStr); return false; } mLog.i(methodStr); return true; } /** Remove downstream prefix from offload management process. */ public boolean removeDownstream(@NonNull String ifname, @NonNull String prefix) { final String methodStr = String.format("removeDownstream(%s, %s)", ifname, prefix); try { mIOffload.removeDownstream(ifname, prefix); } catch (Exception e) { logAndIgnoreException(e, methodStr); return false; } mLog.i(methodStr); return true; } /** * Get {@link IOffloadHal} object from the AIDL service. * * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. * @param log Log to be used by the repository. */ public static IOffloadHal getIOffloadHal(Handler handler, SharedLog log) { // Tetheroffload AIDL interface is only supported after U. if (!SdkLevel.isAtLeastU() || !ServiceManager.isDeclared(HAL_INSTANCE_NAME)) return null; IOffload offload = IOffload.Stub.asInterface( ServiceManager.waitForDeclaredService(HAL_INSTANCE_NAME)); if (offload == null) return null; return new OffloadHalAidlImpl(OFFLOAD_HAL_VERSION_AIDL, offload, handler, log); } private void logAndIgnoreException(Exception e, final String methodStr) { mLog.e(methodStr + " failed with " + e.getClass().getSimpleName() + ": ", e); } private static class TetheringOffloadCallback extends ITetheringOffloadCallback.Stub { public final Handler handler; public final OffloadHalCallback callback; public final SharedLog log; TetheringOffloadCallback( Handler h, OffloadHalCallback cb, SharedLog sharedLog) { handler = h; callback = cb; log = sharedLog; } private void handleOnEvent(int event) { switch (event) { case OffloadCallbackEvent.OFFLOAD_STARTED: callback.onStarted(); break; case OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR: callback.onStoppedError(); break; case OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED: callback.onStoppedUnsupported(); break; case OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE: callback.onSupportAvailable(); break; case OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED: callback.onStoppedLimitReached(); break; case OffloadCallbackEvent.OFFLOAD_WARNING_REACHED: callback.onWarningReached(); break; default: log.e("Unsupported OffloadCallbackEvent: " + event); } } @Override public void onEvent(int event) { handler.post(() -> { handleOnEvent(event); }); } @Override public void updateTimeout(NatTimeoutUpdate params) { handler.post(() -> { callback.onNatTimeoutUpdate( networkProtocolToOsConstant(params.proto), params.src.addr, params.src.port, params.dst.addr, params.dst.port); }); } @Override public String getInterfaceHash() { return ITetheringOffloadCallback.HASH; } @Override public int getInterfaceVersion() { return ITetheringOffloadCallback.VERSION; } } private static int networkProtocolToOsConstant(int proto) { switch (proto) { case NetworkProtocol.TCP: return OsConstants.IPPROTO_TCP; case NetworkProtocol.UDP: return OsConstants.IPPROTO_UDP; default: // The caller checks this value and will log an error. Just make // sure it won't collide with valid OsConstants.IPPROTO_* values. return -Math.abs(proto); } } }