blob: f147e1036d5723a25e8cf6c18b8a9a53d7b38411 [file] [log] [blame]
/*
* 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.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()) {
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()) {
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 getTetheringLooper() {
final HandlerThread tetherThread = new HandlerThread("android.tethering");
tetherThread.start();
return tetherThread.getLooper();
}
@Override
public Context getContext() {
return TetheringService.this;
}
@Override
public IpServer.Dependencies getIpServerDependencies() {
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() {
return BluetoothAdapter.getDefaultAdapter();
}
};
}
}