/* * Copyright (C) 2019 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 android.Manifest.permission.ACCESS_NETWORK_STATE; import static android.Manifest.permission.NETWORK_SETTINGS; import static android.Manifest.permission.NETWORK_STACK; import static android.Manifest.permission.TETHER_PRIVILEGED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION; import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION; import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED; import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR; import android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothManager; import android.content.Context; import android.content.Intent; import android.net.IIntResultListener; import android.net.INetworkStackConnector; import android.net.ITetheringConnector; import android.net.ITetheringEventCallback; import android.net.NetworkStack; import android.net.TetheringRequestParcel; import android.net.dhcp.DhcpServerCallbacks; import android.net.dhcp.DhcpServingParamsParcel; import android.net.ip.IpServer; import android.os.Binder; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.ResultReceiver; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.networkstack.apishim.SettingsShimImpl; import com.android.networkstack.apishim.common.SettingsShim; import java.io.FileDescriptor; import java.io.PrintWriter; /** * Android service used to manage tethering. * * <p>The service returns a binder for the system server to communicate with the tethering. */ public class TetheringService extends Service { private static final String TAG = TetheringService.class.getSimpleName(); private TetheringConnector mConnector; private SettingsShim mSettingsShim; @Override public void onCreate() { final TetheringDependencies deps = makeTetheringDependencies(); // The Tethering object needs a fully functional context to start, so this can't be done // in the constructor. mConnector = new TetheringConnector(makeTethering(deps), TetheringService.this); mSettingsShim = SettingsShimImpl.newInstance(); } /** * Make a reference to Tethering object. */ @VisibleForTesting public Tethering makeTethering(TetheringDependencies deps) { return new Tethering(deps); } @NonNull @Override public IBinder onBind(Intent intent) { return mConnector; } private static class TetheringConnector extends ITetheringConnector.Stub { private final TetheringService mService; private final Tethering mTethering; TetheringConnector(Tethering tether, TetheringService service) { mTethering = tether; mService = service; } @Override public void tether(String iface, String callerPkg, String callingAttributionTag, IIntResultListener listener) { if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; mTethering.tether(iface, IpServer.STATE_TETHERED, listener); } @Override public void untether(String iface, String callerPkg, String callingAttributionTag, IIntResultListener listener) { if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; mTethering.untether(iface, listener); } @Override public void setUsbTethering(boolean enable, String callerPkg, String callingAttributionTag, IIntResultListener listener) { if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; mTethering.setUsbTethering(enable, listener); } @Override public void startTethering(TetheringRequestParcel request, String callerPkg, String callingAttributionTag, IIntResultListener listener) { if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, request.exemptFromEntitlementCheck /* onlyAllowPrivileged */, listener)) { return; } mTethering.startTethering(request, callerPkg, listener); } @Override public void stopTethering(int type, String callerPkg, String callingAttributionTag, IIntResultListener listener) { if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; try { mTethering.stopTethering(type); listener.onResult(TETHER_ERROR_NO_ERROR); } catch (RemoteException e) { } } @Override public void requestLatestTetheringEntitlementResult(int type, ResultReceiver receiver, boolean showEntitlementUi, String callerPkg, String callingAttributionTag) { if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, receiver)) return; mTethering.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi); } @Override public void registerTetheringEventCallback(ITetheringEventCallback callback, String callerPkg) { try { if (!hasTetherAccessPermission()) { callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION); return; } mTethering.registerTetheringEventCallback(callback); } catch (RemoteException e) { } } @Override public void unregisterTetheringEventCallback(ITetheringEventCallback callback, String callerPkg) { try { if (!hasTetherAccessPermission()) { callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION); return; } mTethering.unregisterTetheringEventCallback(callback); } catch (RemoteException e) { } } @Override public void stopAllTethering(String callerPkg, String callingAttributionTag, IIntResultListener listener) { if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; try { mTethering.untetherAll(); listener.onResult(TETHER_ERROR_NO_ERROR); } catch (RemoteException e) { } } @Override public void isTetheringSupported(String callerPkg, String callingAttributionTag, IIntResultListener listener) { if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; try { listener.onResult(TETHER_ERROR_NO_ERROR); } catch (RemoteException e) { } } @Override public void setPreferTestNetworks(boolean prefer, IIntResultListener listener) { if (!checkCallingOrSelfPermission(NETWORK_SETTINGS)) { try { listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); } catch (RemoteException e) { } return; } mTethering.setPreferTestNetworks(prefer, listener); } @Override protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) { mTethering.dump(fd, writer, args); } private boolean checkAndNotifyCommonError(final String callerPkg, final String callingAttributionTag, final IIntResultListener listener) { return checkAndNotifyCommonError(callerPkg, callingAttributionTag, false /* onlyAllowPrivileged */, listener); } private boolean checkAndNotifyCommonError(final String callerPkg, final String callingAttributionTag, final boolean onlyAllowPrivileged, final IIntResultListener listener) { try { if (!hasTetherChangePermission(callerPkg, callingAttributionTag, onlyAllowPrivileged)) { listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); return true; } if (!mTethering.isTetheringSupported() || !mTethering.isTetheringAllowed()) { listener.onResult(TETHER_ERROR_UNSUPPORTED); return true; } } catch (RemoteException e) { return true; } return false; } private boolean checkAndNotifyCommonError(final String callerPkg, final String callingAttributionTag, final ResultReceiver receiver) { if (!hasTetherChangePermission(callerPkg, callingAttributionTag, false /* onlyAllowPrivileged */)) { receiver.send(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, null); return true; } if (!mTethering.isTetheringSupported() || !mTethering.isTetheringAllowed()) { receiver.send(TETHER_ERROR_UNSUPPORTED, null); return true; } return false; } private boolean hasNetworkStackPermission() { return checkCallingOrSelfPermission(NETWORK_STACK) || checkCallingOrSelfPermission(PERMISSION_MAINLINE_NETWORK_STACK); } private boolean hasTetherPrivilegedPermission() { return checkCallingOrSelfPermission(TETHER_PRIVILEGED); } private boolean checkCallingOrSelfPermission(final String permission) { return mService.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED; } private boolean hasTetherChangePermission(final String callerPkg, final String callingAttributionTag, final boolean onlyAllowPrivileged) { if (onlyAllowPrivileged && !hasNetworkStackPermission()) return false; if (hasTetherPrivilegedPermission()) return true; if (mTethering.isTetherProvisioningRequired()) return false; int uid = Binder.getCallingUid(); // If callerPkg's uid is not same as Binder.getCallingUid(), // checkAndNoteWriteSettingsOperation will return false and the operation will be // denied. return mService.checkAndNoteWriteSettingsOperation(mService, uid, callerPkg, callingAttributionTag, false /* throwException */); } private boolean hasTetherAccessPermission() { if (hasTetherPrivilegedPermission()) return true; return mService.checkCallingOrSelfPermission( ACCESS_NETWORK_STATE) == PERMISSION_GRANTED; } } /** * Check if the package is a allowed to write settings. This also accounts that such an access * happened. * * @return {@code true} iff the package is allowed to write settings. */ @VisibleForTesting boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid, @NonNull String callingPackage, @Nullable String callingAttributionTag, boolean throwException) { return mSettingsShim.checkAndNoteWriteSettingsOperation(context, uid, callingPackage, callingAttributionTag, throwException); } /** * An injection method for testing. */ @VisibleForTesting public TetheringDependencies makeTetheringDependencies() { return new TetheringDependencies() { @Override public Looper makeTetheringLooper() { final HandlerThread tetherThread = new HandlerThread("android.tethering"); tetherThread.start(); return tetherThread.getLooper(); } @Override public Context getContext() { return TetheringService.this; } @Override public IpServer.Dependencies makeIpServerDependencies() { return new IpServer.Dependencies() { @Override public void makeDhcpServer(String ifName, DhcpServingParamsParcel params, DhcpServerCallbacks cb) { try { final INetworkStackConnector service = getNetworkStackConnector(); if (service == null) return; service.makeDhcpServer(ifName, params, cb); } catch (RemoteException e) { Log.e(TAG, "Fail to make dhcp server"); try { cb.onDhcpServerCreated(STATUS_UNKNOWN_ERROR, null); } catch (RemoteException re) { } } } }; } // TODO: replace this by NetworkStackClient#getRemoteConnector after refactoring // networkStackClient. static final int NETWORKSTACK_TIMEOUT_MS = 60_000; private INetworkStackConnector getNetworkStackConnector() { IBinder connector; try { final long before = System.currentTimeMillis(); while ((connector = NetworkStack.getService()) == null) { if (System.currentTimeMillis() - before > NETWORKSTACK_TIMEOUT_MS) { Log.wtf(TAG, "Timeout, fail to get INetworkStackConnector"); return null; } Thread.sleep(200); } } catch (InterruptedException e) { Log.wtf(TAG, "Interrupted, fail to get INetworkStackConnector"); return null; } return INetworkStackConnector.Stub.asInterface(connector); } @Override public BluetoothAdapter getBluetoothAdapter() { final BluetoothManager btManager = getSystemService(BluetoothManager.class); if (btManager == null) { return null; } return btManager.getAdapter(); } }; } }