Merge "Stop reducing RA advertised ipv6 mtu by 16 - not needed."
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 33c2b58..4116afc 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -25,7 +25,7 @@
],
static_libs: [
"androidx.annotation_annotation",
- "netd_aidl_interface-V3-java",
+ "netd_aidl_interface-unstable-java",
"netlink-client",
"networkstack-aidl-interfaces-java",
"android.hardware.tetheroffload.config-V1.0-java",
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index 408725c..c8becce 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -16,21 +16,24 @@
java_sdk_library {
name: "framework-tethering",
defaults: ["framework-module-defaults"],
+
+ // Allow access to the stubs from anywhere.
+ visibility: ["//visibility:public"],
+
+ // Restrict access to implementation library.
+ impl_library_visibility: [
+ "//visibility:override", // Ignore the visibility property.
+ "//frameworks/base/packages/Tethering:__subpackages__",
+ ],
+
srcs: [
":framework-tethering-srcs",
],
- // TODO(b/155480189) - Remove naming_scheme once references have been resolved.
- // Temporary java_sdk_library component naming scheme to use to ease the transition from separate
- // modules to java_sdk_library.
- naming_scheme: "framework-modules",
-
jarjar_rules: "jarjar-rules.txt",
installable: true,
hostdex: true, // for hiddenapi check
- visibility: ["//frameworks/base/packages/Tethering:__subpackages__"],
- stubs_library_visibility: ["//visibility:public"],
apex_available: ["com.android.tethering"],
permitted_packages: ["android.net"],
}
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 3fd9ee9..830f6a0 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -33,7 +33,6 @@
import android.net.LinkProperties;
import android.net.MacAddress;
import android.net.RouteInfo;
-import android.net.TetherOffloadRuleParcel;
import android.net.TetheredClient;
import android.net.TetheringManager;
import android.net.TetheringRequestParcel;
@@ -65,6 +64,8 @@
import com.android.internal.util.MessageUtils;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
+import com.android.networkstack.tethering.BpfCoordinator;
+import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
import java.io.IOException;
@@ -225,6 +226,8 @@
private final SharedLog mLog;
private final INetd mNetd;
+ @NonNull
+ private final BpfCoordinator mBpfCoordinator;
private final Callback mCallback;
private final InterfaceController mInterfaceCtrl;
private final PrivateAddressCoordinator mPrivateAddressCoordinator;
@@ -269,40 +272,6 @@
}
}
- static class Ipv6ForwardingRule {
- public final int upstreamIfindex;
- public final int downstreamIfindex;
- public final Inet6Address address;
- public final MacAddress srcMac;
- public final MacAddress dstMac;
-
- Ipv6ForwardingRule(int upstreamIfindex, int downstreamIfIndex, Inet6Address address,
- MacAddress srcMac, MacAddress dstMac) {
- this.upstreamIfindex = upstreamIfindex;
- this.downstreamIfindex = downstreamIfIndex;
- this.address = address;
- this.srcMac = srcMac;
- this.dstMac = dstMac;
- }
-
- public Ipv6ForwardingRule onNewUpstream(int newUpstreamIfindex) {
- return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac,
- dstMac);
- }
-
- // Don't manipulate TetherOffloadRuleParcel directly because implementing onNewUpstream()
- // would be error-prone due to generated stable AIDL classes not having a copy constructor.
- public TetherOffloadRuleParcel toTetherOffloadRuleParcel() {
- final TetherOffloadRuleParcel parcel = new TetherOffloadRuleParcel();
- parcel.inputInterfaceIndex = upstreamIfindex;
- parcel.outputInterfaceIndex = downstreamIfindex;
- parcel.destination = address.getAddress();
- parcel.prefixLength = 128;
- parcel.srcL2Address = srcMac.toByteArray();
- parcel.dstL2Address = dstMac.toByteArray();
- return parcel;
- }
- }
private final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> mIpv6ForwardingRules =
new LinkedHashMap<>();
@@ -314,11 +283,13 @@
// object. It helps to reduce the arguments of the constructor.
public IpServer(
String ifaceName, Looper looper, int interfaceType, SharedLog log,
- INetd netd, Callback callback, boolean usingLegacyDhcp, boolean usingBpfOffload,
+ INetd netd, @NonNull BpfCoordinator coordinator, Callback callback,
+ boolean usingLegacyDhcp, boolean usingBpfOffload,
PrivateAddressCoordinator addressCoordinator, Dependencies deps) {
super(ifaceName, looper);
mLog = log.forSubComponent(ifaceName);
mNetd = netd;
+ mBpfCoordinator = coordinator;
mCallback = callback;
mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog);
mIfaceName = ifaceName;
@@ -749,6 +720,14 @@
}
upstreamIfindex = mDeps.getIfindex(upstreamIface);
+
+ // Add upstream index to name mapping for the tether stats usage in the coordinator.
+ // Although this mapping could be added by both class Tethering and IpServer, adding
+ // mapping from IpServer guarantees that the mapping is added before the adding
+ // forwarding rules. That is because there are different state machines in both
+ // classes. It is hard to guarantee the link property update order between multiple
+ // state machines.
+ mBpfCoordinator.addUpstreamNameToLookupTable(upstreamIfindex, upstreamIface);
}
// If v6only is null, we pass in null to setRaParams(), which handles
diff --git a/Tethering/src/android/net/util/TetheringUtils.java b/Tethering/src/android/net/util/TetheringUtils.java
index dd67ddd..b17b4ba 100644
--- a/Tethering/src/android/net/util/TetheringUtils.java
+++ b/Tethering/src/android/net/util/TetheringUtils.java
@@ -15,19 +15,94 @@
*/
package android.net.util;
+import android.net.TetherStatsParcel;
import android.net.TetheringRequestParcel;
+import androidx.annotation.NonNull;
+
import java.io.FileDescriptor;
import java.net.SocketException;
import java.util.Objects;
/**
- * Native methods for tethering utilization.
+ * The classes and the methods for tethering utilization.
*
* {@hide}
*/
public class TetheringUtils {
/**
+ * The object which records offload Tx/Rx forwarded bytes/packets.
+ * TODO: Replace the inner class ForwardedStats of class OffloadHardwareInterface with
+ * this class as well.
+ */
+ public static class ForwardedStats {
+ public final long rxBytes;
+ public final long rxPackets;
+ public final long txBytes;
+ public final long txPackets;
+
+ public ForwardedStats() {
+ rxBytes = 0;
+ rxPackets = 0;
+ txBytes = 0;
+ txPackets = 0;
+ }
+
+ public ForwardedStats(long rxBytes, long txBytes) {
+ this.rxBytes = rxBytes;
+ this.rxPackets = 0;
+ this.txBytes = txBytes;
+ this.txPackets = 0;
+ }
+
+ public ForwardedStats(long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ this.rxBytes = rxBytes;
+ this.rxPackets = rxPackets;
+ this.txBytes = txBytes;
+ this.txPackets = txPackets;
+ }
+
+ public ForwardedStats(@NonNull TetherStatsParcel tetherStats) {
+ rxBytes = tetherStats.rxBytes;
+ rxPackets = tetherStats.rxPackets;
+ txBytes = tetherStats.txBytes;
+ txPackets = tetherStats.txPackets;
+ }
+
+ public ForwardedStats(@NonNull ForwardedStats other) {
+ rxBytes = other.rxBytes;
+ rxPackets = other.rxPackets;
+ txBytes = other.txBytes;
+ txPackets = other.txPackets;
+ }
+
+ /** Add Tx/Rx bytes/packets and return the result as a new object. */
+ @NonNull
+ public ForwardedStats add(@NonNull ForwardedStats other) {
+ return new ForwardedStats(rxBytes + other.rxBytes, rxPackets + other.rxPackets,
+ txBytes + other.txBytes, txPackets + other.txPackets);
+ }
+
+ /** Subtract Tx/Rx bytes/packets and return the result as a new object. */
+ @NonNull
+ public ForwardedStats subtract(@NonNull ForwardedStats other) {
+ // TODO: Perhaps throw an exception if any negative difference value just in case.
+ final long rxBytesDiff = Math.max(rxBytes - other.rxBytes, 0);
+ final long rxPacketsDiff = Math.max(rxPackets - other.rxPackets, 0);
+ final long txBytesDiff = Math.max(txBytes - other.txBytes, 0);
+ final long txPacketsDiff = Math.max(txPackets - other.txPackets, 0);
+ return new ForwardedStats(rxBytesDiff, rxPacketsDiff, txBytesDiff, txPacketsDiff);
+ }
+
+ /** Returns the string representation of this object. */
+ @NonNull
+ public String toString() {
+ return String.format("ForwardedStats(rxb: %d, rxp: %d, txb: %d, txp: %d)", rxBytes,
+ rxPackets, txBytes, txPackets);
+ }
+ }
+
+ /**
* Configures a socket for receiving ICMPv6 router solicitations and sending advertisements.
* @param fd the socket's {@link FileDescriptor}.
* @param ifIndex the interface index.
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
new file mode 100644
index 0000000..6b854f2
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2020 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.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkStats.UID_TETHERING;
+
+import android.app.usage.NetworkStatsManager;
+import android.net.INetd;
+import android.net.MacAddress;
+import android.net.NetworkStats;
+import android.net.NetworkStats.Entry;
+import android.net.TetherOffloadRuleParcel;
+import android.net.TetherStatsParcel;
+import android.net.netstats.provider.NetworkStatsProvider;
+import android.net.util.SharedLog;
+import android.net.util.TetheringUtils.ForwardedStats;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+import android.util.SparseArray;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.Inet6Address;
+
+/**
+ * This coordinator is responsible for providing BPF offload relevant functionality.
+ * - Get tethering stats.
+ *
+ * @hide
+ */
+public class BpfCoordinator {
+ private static final String TAG = BpfCoordinator.class.getSimpleName();
+ @VisibleForTesting
+ static final int DEFAULT_PERFORM_POLL_INTERVAL_MS = 5000; // TODO: Make it customizable.
+
+ @VisibleForTesting
+ enum StatsType {
+ STATS_PER_IFACE,
+ STATS_PER_UID,
+ }
+
+ @NonNull
+ private final Handler mHandler;
+ @NonNull
+ private final INetd mNetd;
+ @NonNull
+ private final SharedLog mLog;
+ @NonNull
+ private final Dependencies mDeps;
+ @Nullable
+ private final BpfTetherStatsProvider mStatsProvider;
+ private boolean mStarted = false;
+
+ // Maps upstream interface index to offloaded traffic statistics.
+ // Always contains the latest total bytes/packets, since each upstream was started, received
+ // from the BPF maps for each interface.
+ private SparseArray<ForwardedStats> mStats = new SparseArray<>();
+
+ // Maps upstream interface index to interface names.
+ // Store all interface name since boot. Used for lookup what interface name it is from the
+ // tether stats got from netd because netd reports interface index to present an interface.
+ // TODO: Remove the unused interface name.
+ private SparseArray<String> mInterfaceNames = new SparseArray<>();
+
+ // Runnable that used by scheduling next polling of stats.
+ private final Runnable mScheduledPollingTask = () -> {
+ updateForwardedStatsFromNetd();
+ maybeSchedulePollingStats();
+ };
+
+ @VisibleForTesting
+ static class Dependencies {
+ int getPerformPollInterval() {
+ // TODO: Consider make this configurable.
+ return DEFAULT_PERFORM_POLL_INTERVAL_MS;
+ }
+ }
+
+ BpfCoordinator(@NonNull Handler handler, @NonNull INetd netd,
+ @NonNull NetworkStatsManager nsm, @NonNull SharedLog log, @NonNull Dependencies deps) {
+ mHandler = handler;
+ mNetd = netd;
+ mLog = log.forSubComponent(TAG);
+ BpfTetherStatsProvider provider = new BpfTetherStatsProvider();
+ try {
+ nsm.registerNetworkStatsProvider(getClass().getSimpleName(), provider);
+ } catch (RuntimeException e) {
+ // TODO: Perhaps not allow to use BPF offload because the reregistration failure
+ // implied that no data limit could be applies on a metered upstream if any.
+ Log.wtf(TAG, "Cannot register offload stats provider: " + e);
+ provider = null;
+ }
+ mStatsProvider = provider;
+ mDeps = deps;
+ }
+
+ /**
+ * Start BPF tethering offload stats polling when the first upstream is started.
+ * Note that this can be only called on handler thread.
+ * TODO: Perhaps check BPF support before starting.
+ * TODO: Start the stats polling only if there is any client on the downstream.
+ */
+ public void start() {
+ if (mStarted) return;
+
+ mStarted = true;
+ maybeSchedulePollingStats();
+
+ mLog.i("BPF tethering coordinator started");
+ }
+
+ /**
+ * Stop BPF tethering offload stats polling and cleanup upstream parameters.
+ * Note that this can be only called on handler thread.
+ */
+ public void stop() {
+ if (!mStarted) return;
+
+ // Stop scheduled polling tasks and poll the latest stats from BPF maps.
+ if (mHandler.hasCallbacks(mScheduledPollingTask)) {
+ mHandler.removeCallbacks(mScheduledPollingTask);
+ }
+ updateForwardedStatsFromNetd();
+
+ mStarted = false;
+
+ mLog.i("BPF tethering coordinator stopped");
+ }
+
+ /**
+ * Add upstream name to lookup table. The lookup table is used for tether stats interface name
+ * lookup because the netd only reports interface index in BPF tether stats but the service
+ * expects the interface name in NetworkStats object.
+ * Note that this can be only called on handler thread.
+ */
+ public void addUpstreamNameToLookupTable(int upstreamIfindex, String upstreamIface) {
+ if (upstreamIfindex == 0) return;
+
+ // The same interface index to name mapping may be added by different IpServer objects or
+ // re-added by reconnection on the same upstream interface. Ignore the duplicate one.
+ final String iface = mInterfaceNames.get(upstreamIfindex);
+ if (iface == null) {
+ mInterfaceNames.put(upstreamIfindex, upstreamIface);
+ } else if (iface != upstreamIface) {
+ Log.wtf(TAG, "The upstream interface name " + upstreamIface
+ + " is different from the existing interface name "
+ + iface + " for index " + upstreamIfindex);
+ }
+ }
+
+ /** IPv6 forwarding rule class. */
+ public static class Ipv6ForwardingRule {
+ public final int upstreamIfindex;
+ public final int downstreamIfindex;
+ public final Inet6Address address;
+ public final MacAddress srcMac;
+ public final MacAddress dstMac;
+
+ public Ipv6ForwardingRule(int upstreamIfindex, int downstreamIfIndex, Inet6Address address,
+ MacAddress srcMac, MacAddress dstMac) {
+ this.upstreamIfindex = upstreamIfindex;
+ this.downstreamIfindex = downstreamIfIndex;
+ this.address = address;
+ this.srcMac = srcMac;
+ this.dstMac = dstMac;
+ }
+
+ /** Return a new rule object which updates with new upstream index. */
+ public Ipv6ForwardingRule onNewUpstream(int newUpstreamIfindex) {
+ return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac,
+ dstMac);
+ }
+
+ /**
+ * Don't manipulate TetherOffloadRuleParcel directly because implementing onNewUpstream()
+ * would be error-prone due to generated stable AIDL classes not having a copy constructor.
+ */
+ public TetherOffloadRuleParcel toTetherOffloadRuleParcel() {
+ final TetherOffloadRuleParcel parcel = new TetherOffloadRuleParcel();
+ parcel.inputInterfaceIndex = upstreamIfindex;
+ parcel.outputInterfaceIndex = downstreamIfindex;
+ parcel.destination = address.getAddress();
+ parcel.prefixLength = 128;
+ parcel.srcL2Address = srcMac.toByteArray();
+ parcel.dstL2Address = dstMac.toByteArray();
+ return parcel;
+ }
+ }
+
+ /**
+ * A BPF tethering stats provider to provide network statistics to the system.
+ * Note that this class's data may only be accessed on the handler thread.
+ */
+ @VisibleForTesting
+ class BpfTetherStatsProvider extends NetworkStatsProvider {
+ // The offloaded traffic statistics per interface that has not been reported since the
+ // last call to pushTetherStats. Only the interfaces that were ever tethering upstreams
+ // and has pending tether stats delta are included in this NetworkStats object.
+ private NetworkStats mIfaceStats = new NetworkStats(0L, 0);
+
+ // The same stats as above, but counts network stats per uid.
+ private NetworkStats mUidStats = new NetworkStats(0L, 0);
+
+ @Override
+ public void onRequestStatsUpdate(int token) {
+ mHandler.post(() -> pushTetherStats());
+ }
+
+ @Override
+ public void onSetAlert(long quotaBytes) {
+ // no-op
+ }
+
+ @Override
+ public void onSetLimit(@NonNull String iface, long quotaBytes) {
+ // no-op
+ }
+
+ @VisibleForTesting
+ void pushTetherStats() {
+ try {
+ // The token is not used for now. See b/153606961.
+ notifyStatsUpdated(0 /* token */, mIfaceStats, mUidStats);
+
+ // Clear the accumulated tether stats delta after reported. Note that create a new
+ // empty object because NetworkStats#clear is @hide.
+ mIfaceStats = new NetworkStats(0L, 0);
+ mUidStats = new NetworkStats(0L, 0);
+ } catch (RuntimeException e) {
+ mLog.e("Cannot report network stats: ", e);
+ }
+ }
+
+ private void accumulateDiff(@NonNull NetworkStats ifaceDiff,
+ @NonNull NetworkStats uidDiff) {
+ mIfaceStats = mIfaceStats.add(ifaceDiff);
+ mUidStats = mUidStats.add(uidDiff);
+ }
+ }
+
+ @NonNull
+ private NetworkStats buildNetworkStats(@NonNull StatsType type, int ifIndex,
+ @NonNull ForwardedStats diff) {
+ NetworkStats stats = new NetworkStats(0L, 0);
+ final String iface = mInterfaceNames.get(ifIndex);
+ if (iface == null) {
+ // TODO: Use Log.wtf once the coordinator owns full control of tether stats from netd.
+ // For now, netd may add the empty stats for the upstream which is not monitored by
+ // the coordinator. Silently ignore it.
+ return stats;
+ }
+ final int uid = (type == StatsType.STATS_PER_UID) ? UID_TETHERING : UID_ALL;
+ // Note that the argument 'metered', 'roaming' and 'defaultNetwork' are not recorded for
+ // network stats snapshot. See NetworkStatsRecorder#recordSnapshotLocked.
+ return stats.addEntry(new Entry(iface, uid, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, diff.rxBytes, diff.rxPackets,
+ diff.txBytes, diff.txPackets, 0L /* operations */));
+ }
+
+ private void updateForwardedStatsFromNetd() {
+ final TetherStatsParcel[] tetherStatsList;
+ try {
+ // The reported tether stats are total data usage for all currently-active upstream
+ // interfaces since tethering start.
+ tetherStatsList = mNetd.tetherOffloadGetStats();
+ } catch (RemoteException | ServiceSpecificException e) {
+ mLog.e("Problem fetching tethering stats: ", e);
+ return;
+ }
+
+ for (TetherStatsParcel tetherStats : tetherStatsList) {
+ final Integer ifIndex = tetherStats.ifIndex;
+ final ForwardedStats curr = new ForwardedStats(tetherStats);
+ final ForwardedStats base = mStats.get(ifIndex);
+ final ForwardedStats diff = (base != null) ? curr.subtract(base) : curr;
+
+ // Update the local cache for counting tether stats delta.
+ mStats.put(ifIndex, curr);
+
+ // Update the accumulated tether stats delta to the stats provider for the service
+ // querying.
+ if (mStatsProvider != null) {
+ try {
+ mStatsProvider.accumulateDiff(
+ buildNetworkStats(StatsType.STATS_PER_IFACE, ifIndex, diff),
+ buildNetworkStats(StatsType.STATS_PER_UID, ifIndex, diff));
+ } catch (ArrayIndexOutOfBoundsException e) {
+ Log.wtf("Fail to update the accumulated stats delta for interface index "
+ + ifIndex + " : ", e);
+ }
+ }
+ }
+ }
+
+ private void maybeSchedulePollingStats() {
+ if (!mStarted) return;
+
+ if (mHandler.hasCallbacks(mScheduledPollingTask)) {
+ mHandler.removeCallbacks(mScheduledPollingTask);
+ }
+
+ mHandler.postDelayed(mScheduledPollingTask, mDeps.getPerformPollInterval());
+ }
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 6eb1012..00723ac 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -232,6 +232,7 @@
private final TetheringThreadExecutor mExecutor;
private final TetheringNotificationUpdater mNotificationUpdater;
private final UserManager mUserManager;
+ private final BpfCoordinator mBpfCoordinator;
private final PrivateAddressCoordinator mPrivateAddressCoordinator;
private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
// All the usage of mTetheringEventCallback should run in the same thread.
@@ -284,6 +285,8 @@
mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog,
TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
mForwardedDownstreams = new LinkedHashSet<>();
+ mBpfCoordinator = mDeps.getBpfCoordinator(
+ mHandler, mNetd, mLog, new BpfCoordinator.Dependencies());
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_CARRIER_CONFIG_CHANGED);
@@ -1704,6 +1707,9 @@
chooseUpstreamType(true);
mTryCell = false;
}
+
+ // TODO: Check the upstream interface if it is managed by BPF offload.
+ mBpfCoordinator.start();
}
@Override
@@ -1716,6 +1722,7 @@
mTetherUpstream = null;
reportUpstreamChanged(null);
}
+ mBpfCoordinator.stop();
}
private boolean updateUpstreamWanted() {
@@ -2341,7 +2348,7 @@
mLog.log("adding TetheringInterfaceStateMachine for: " + iface);
final TetherState tetherState = new TetherState(
- new IpServer(iface, mLooper, interfaceType, mLog, mNetd,
+ new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mBpfCoordinator,
makeControlCallback(), mConfig.enableLegacyDhcpServer,
mConfig.enableBpfOffload, mPrivateAddressCoordinator,
mDeps.getIpServerDependencies()));
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index ce546c7..d637c86 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -41,6 +41,17 @@
*/
public abstract class TetheringDependencies {
/**
+ * Get a reference to the BpfCoordinator to be used by tethering.
+ */
+ public @NonNull BpfCoordinator getBpfCoordinator(
+ @NonNull Handler handler, @NonNull INetd netd, @NonNull SharedLog log,
+ @NonNull BpfCoordinator.Dependencies deps) {
+ final NetworkStatsManager statsManager =
+ (NetworkStatsManager) getContext().getSystemService(Context.NETWORK_STATS_SERVICE);
+ return new BpfCoordinator(handler, netd, statsManager, log, deps);
+ }
+
+ /**
* Get a reference to the offload hardware interface to be used by tethering.
*/
public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) {
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index c11e862..613328d 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -17,8 +17,10 @@
package com.android.networkstack.tethering;
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
+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;
@@ -253,15 +255,26 @@
return false;
}
+ private boolean hasNetworkStackPermission() {
+ return checkCallingOrSelfPermission(NETWORK_STACK)
+ || checkCallingOrSelfPermission(PERMISSION_MAINLINE_NETWORK_STACK);
+ }
+
private boolean hasTetherPrivilegedPermission() {
- return mService.checkCallingOrSelfPermission(TETHER_PRIVILEGED) == PERMISSION_GRANTED;
+ 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 (onlyAllowPrivileged || mTethering.isTetherProvisioningRequired()) return false;
+ if (mTethering.isTetherProvisioningRequired()) return false;
int uid = Binder.getCallingUid();
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 2fb7e60..74df113 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -339,7 +339,7 @@
private MyTetheringEventCallback enableEthernetTethering(String iface) throws Exception {
return enableEthernetTethering(iface,
new TetheringRequest.Builder(TETHERING_ETHERNET)
- .setExemptFromEntitlementCheck(true).build());
+ .setShouldShowEntitlementUi(false).build());
}
private int getMTU(TestNetworkInterface iface) throws SocketException {
@@ -510,7 +510,7 @@
LinkAddress clientAddr = client == null ? null : new LinkAddress(client);
return new TetheringRequest.Builder(TETHERING_ETHERNET)
.setStaticIpv4Addresses(localAddr, clientAddr)
- .setExemptFromEntitlementCheck(true).build();
+ .setShouldShowEntitlementUi(false).build();
}
private void assertInvalidStaticIpv4Request(String iface, String local, String client)
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 0cda29a..433aacf 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -87,6 +87,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.networkstack.tethering.BpfCoordinator;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
import org.junit.Before;
@@ -126,6 +127,7 @@
private final IpPrefix mBluetoothPrefix = new IpPrefix("192.168.44.0/24");
@Mock private INetd mNetd;
+ @Mock private BpfCoordinator mBpfCoordinator;
@Mock private IpServer.Callback mCallback;
@Mock private SharedLog mSharedLog;
@Mock private IDhcpServer mDhcpServer;
@@ -179,7 +181,7 @@
neighborCaptor.capture());
mIpServer = new IpServer(
- IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd,
+ IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mBpfCoordinator,
mCallback, usingLegacyDhcp, usingBpfOffload, mAddressCoordinator, mDependencies);
mIpServer.start();
mNeighborEventConsumer = neighborCaptor.getValue();
@@ -222,8 +224,8 @@
when(mDependencies.getIpNeighborMonitor(any(), any(), any()))
.thenReturn(mIpNeighborMonitor);
mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog,
- mNetd, mCallback, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD,
- mAddressCoordinator, mDependencies);
+ mNetd, mBpfCoordinator, mCallback, false /* usingLegacyDhcp */,
+ DEFAULT_USING_BPF_OFFLOAD, mAddressCoordinator, mDependencies);
mIpServer.start();
mLooper.dispatchAll();
verify(mCallback).updateInterfaceState(
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
new file mode 100644
index 0000000..b029b43
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2020 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.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkStats.UID_TETHERING;
+
+import static com.android.networkstack.tethering.BpfCoordinator
+ .DEFAULT_PERFORM_POLL_INTERVAL_MS;
+import static com.android.networkstack.tethering.BpfCoordinator.StatsType;
+import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_IFACE;
+import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_UID;
+
+import static junit.framework.Assert.assertNotNull;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.app.usage.NetworkStatsManager;
+import android.net.INetd;
+import android.net.NetworkStats;
+import android.net.TetherStatsParcel;
+import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.TestableNetworkStatsProviderCbBinder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BpfCoordinatorTest {
+ @Mock private NetworkStatsManager mStatsManager;
+ @Mock private INetd mNetd;
+ // Late init since methods must be called by the thread that created this object.
+ private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb;
+ private BpfCoordinator.BpfTetherStatsProvider mTetherStatsProvider;
+ private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
+ ArgumentCaptor.forClass(ArrayList.class);
+ private final TestLooper mTestLooper = new TestLooper();
+ private BpfCoordinator.Dependencies mDeps =
+ new BpfCoordinator.Dependencies() {
+ @Override
+ int getPerformPollInterval() {
+ return DEFAULT_PERFORM_POLL_INTERVAL_MS;
+ }
+ };
+
+ @Before public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ private void waitForIdle() {
+ mTestLooper.dispatchAll();
+ }
+
+ private void setupFunctioningNetdInterface() throws Exception {
+ when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]);
+ }
+
+ @NonNull
+ private BpfCoordinator makeBpfCoordinator() throws Exception {
+ BpfCoordinator coordinator = new BpfCoordinator(
+ new Handler(mTestLooper.getLooper()), mNetd, mStatsManager, new SharedLog("test"),
+ mDeps);
+ final ArgumentCaptor<BpfCoordinator.BpfTetherStatsProvider>
+ tetherStatsProviderCaptor =
+ ArgumentCaptor.forClass(BpfCoordinator.BpfTetherStatsProvider.class);
+ verify(mStatsManager).registerNetworkStatsProvider(anyString(),
+ tetherStatsProviderCaptor.capture());
+ mTetherStatsProvider = tetherStatsProviderCaptor.getValue();
+ assertNotNull(mTetherStatsProvider);
+ mTetherStatsProviderCb = new TestableNetworkStatsProviderCbBinder();
+ mTetherStatsProvider.setProviderCallbackBinder(mTetherStatsProviderCb);
+ return coordinator;
+ }
+
+ @NonNull
+ private static NetworkStats.Entry buildTestEntry(@NonNull StatsType how,
+ @NonNull String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ return new NetworkStats.Entry(iface, how == STATS_PER_IFACE ? UID_ALL : UID_TETHERING,
+ SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes,
+ rxPackets, txBytes, txPackets, 0L);
+ }
+
+ @NonNull
+ private static TetherStatsParcel buildTestTetherStatsParcel(@NonNull Integer ifIndex,
+ long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ final TetherStatsParcel parcel = new TetherStatsParcel();
+ parcel.ifIndex = ifIndex;
+ parcel.rxBytes = rxBytes;
+ parcel.rxPackets = rxPackets;
+ parcel.txBytes = txBytes;
+ parcel.txPackets = txPackets;
+ return parcel;
+ }
+
+ private void setTetherOffloadStatsList(TetherStatsParcel[] tetherStatsList) throws Exception {
+ when(mNetd.tetherOffloadGetStats()).thenReturn(tetherStatsList);
+ mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
+ waitForIdle();
+ }
+
+ @Test
+ public void testGetForwardedStats() throws Exception {
+ setupFunctioningNetdInterface();
+
+ final BpfCoordinator coordinator = makeBpfCoordinator();
+ coordinator.start();
+
+ final String wlanIface = "wlan0";
+ final Integer wlanIfIndex = 100;
+ final String mobileIface = "rmnet_data0";
+ final Integer mobileIfIndex = 101;
+
+ // Add interface name to lookup table. In realistic case, the upstream interface name will
+ // be added by IpServer when IpServer has received with a new IPv6 upstream update event.
+ coordinator.addUpstreamNameToLookupTable(wlanIfIndex, wlanIface);
+ coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface);
+
+ // [1] Both interface stats are changed.
+ // Setup the tether stats of wlan and mobile interface. Note that move forward the time of
+ // the looper to make sure the new tether stats has been updated by polling update thread.
+ setTetherOffloadStatsList(new TetherStatsParcel[] {
+ buildTestTetherStatsParcel(wlanIfIndex, 1000, 100, 2000, 200),
+ buildTestTetherStatsParcel(mobileIfIndex, 3000, 300, 4000, 400)});
+
+ final NetworkStats expectedIfaceStats = new NetworkStats(0L, 2)
+ .addEntry(buildTestEntry(STATS_PER_IFACE, wlanIface, 1000, 100, 2000, 200))
+ .addEntry(buildTestEntry(STATS_PER_IFACE, mobileIface, 3000, 300, 4000, 400));
+
+ final NetworkStats expectedUidStats = new NetworkStats(0L, 2)
+ .addEntry(buildTestEntry(STATS_PER_UID, wlanIface, 1000, 100, 2000, 200))
+ .addEntry(buildTestEntry(STATS_PER_UID, mobileIface, 3000, 300, 4000, 400));
+
+ // Force pushing stats update to verify the stats reported.
+ // TODO: Perhaps make #expectNotifyStatsUpdated to use test TetherStatsParcel object for
+ // verifying the notification.
+ mTetherStatsProvider.pushTetherStats();
+ mTetherStatsProviderCb.expectNotifyStatsUpdated(expectedIfaceStats, expectedUidStats);
+
+ // [2] Only one interface stats is changed.
+ // The tether stats of mobile interface is accumulated and The tether stats of wlan
+ // interface is the same.
+ setTetherOffloadStatsList(new TetherStatsParcel[] {
+ buildTestTetherStatsParcel(wlanIfIndex, 1000, 100, 2000, 200),
+ buildTestTetherStatsParcel(mobileIfIndex, 3010, 320, 4030, 440)});
+
+ final NetworkStats expectedIfaceStatsDiff = new NetworkStats(0L, 2)
+ .addEntry(buildTestEntry(STATS_PER_IFACE, wlanIface, 0, 0, 0, 0))
+ .addEntry(buildTestEntry(STATS_PER_IFACE, mobileIface, 10, 20, 30, 40));
+
+ final NetworkStats expectedUidStatsDiff = new NetworkStats(0L, 2)
+ .addEntry(buildTestEntry(STATS_PER_UID, wlanIface, 0, 0, 0, 0))
+ .addEntry(buildTestEntry(STATS_PER_UID, mobileIface, 10, 20, 30, 40));
+
+ // Force pushing stats update to verify that only diff of stats is reported.
+ mTetherStatsProvider.pushTetherStats();
+ mTetherStatsProviderCb.expectNotifyStatsUpdated(expectedIfaceStatsDiff,
+ expectedUidStatsDiff);
+
+ // [3] Stop coordinator.
+ // Shutdown the coordinator and clear the invocation history, especially the
+ // tetherOffloadGetStats() calls.
+ coordinator.stop();
+ clearInvocations(mNetd);
+
+ // Verify the polling update thread stopped.
+ mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
+ waitForIdle();
+ verify(mNetd, never()).tetherOffloadGetStats();
+ }
+}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
index cf060ba..7bba67b 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -281,22 +281,33 @@
});
}
+ private void runStartTetheringAndVerifyNoPermission(final TestTetheringResult result)
+ throws Exception {
+ final TetheringRequestParcel request = new TetheringRequestParcel();
+ request.tetheringType = TETHERING_WIFI;
+ request.exemptFromEntitlementCheck = true;
+ mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+ result);
+ result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+ verifyNoMoreInteractionsForTethering();
+ }
+
@Test
- public void testStartTetheringWithExemptFromEntitlementCheck() throws Exception {
+ public void testFailToBypassEntitlementWithoutNeworkStackPermission() throws Exception {
final TetheringRequestParcel request = new TetheringRequestParcel();
request.tetheringType = TETHERING_WIFI;
request.exemptFromEntitlementCheck = true;
+ runAsNoPermission((result) -> {
+ runStartTetheringAndVerifyNoPermission(result);
+ });
+
runAsTetherPrivileged((result) -> {
- runStartTethering(result, request);
- verifyNoMoreInteractionsForTethering();
+ runStartTetheringAndVerifyNoPermission(result);
});
runAsWriteSettings((result) -> {
- mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
- result);
- result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
- verifyNoMoreInteractionsForTethering();
+ runStartTetheringAndVerifyNoPermission(result);
});
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 5fffaae..329d8a5 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -203,6 +203,7 @@
@Mock private ConnectivityManager mCm;
@Mock private EthernetManager mEm;
@Mock private TetheringNotificationUpdater mNotificationUpdater;
+ @Mock private BpfCoordinator mBpfCoordinator;
private final MockIpServerDependencies mIpServerDependencies =
spy(new MockIpServerDependencies());
@@ -337,6 +338,12 @@
}
@Override
+ public BpfCoordinator getBpfCoordinator(Handler handler, INetd netd,
+ SharedLog log, BpfCoordinator.Dependencies deps) {
+ return mBpfCoordinator;
+ }
+
+ @Override
public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) {
return mOffloadHardwareInterface;
}