Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ec5a7038 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes I48a8de47,I95ea3050,I5a98c4cd into rvc-dev

* changes:
  [BOT.11] BpfCoordinator could be disabled by device config
  [BOT.8] Dump BPF offload information in dumpsys
  [BOT.10] Add unit test for data limit and rule change in BpfCoordinator
parents 53795ae7 3f856b48
Loading
Loading
Loading
Loading
+129 −9
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.net.ip.IpServer;
import android.net.netstats.provider.NetworkStatsProvider;
import android.net.util.SharedLog;
import android.net.util.TetheringUtils.ForwardedStats;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
@@ -47,11 +48,13 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;

import java.net.Inet6Address;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

/**
@@ -65,6 +68,7 @@ import java.util.Objects;
 */
public class BpfCoordinator {
    private static final String TAG = BpfCoordinator.class.getSimpleName();
    private static final int DUMP_TIMEOUT_MS = 10_000;
    @VisibleForTesting
    static final int DEFAULT_PERFORM_POLL_INTERVAL_MS = 5000; // TODO: Make it customizable.

@@ -85,6 +89,13 @@ public class BpfCoordinator {
    @Nullable
    private final BpfTetherStatsProvider mStatsProvider;

    // True if BPF offload is supported, false otherwise. The BPF offload could be disabled by
    // a runtime resource overlay package or device configuration. This flag is only initialized
    // in the constructor because it is hard to unwind all existing change once device
    // configuration is changed. Especially the forwarding rules. Keep the same setting
    // to make it simpler. See also TetheringConfiguration.
    private final boolean mUsingBpf;

    // Tracks whether BPF tethering is started or not. This is set by tethering before it
    // starts the first IpServer and is cleared by tethering shortly before the last IpServer
    // is stopped. Note that rule updates (especially deletions, but sometimes additions as
@@ -142,22 +153,42 @@ public class BpfCoordinator {
    };

    @VisibleForTesting
    public static class Dependencies {
        int getPerformPollInterval() {
    public abstract static class Dependencies {
        /**
         * Get polling Interval in milliseconds.
         */
        public int getPerformPollInterval() {
            // TODO: Consider make this configurable.
            return DEFAULT_PERFORM_POLL_INTERVAL_MS;
        }

        /** Get handler. */
        @NonNull public abstract Handler getHandler();

        /** Get netd. */
        @NonNull public abstract INetd getNetd();

        /** Get network stats manager. */
        @NonNull public abstract NetworkStatsManager getNetworkStatsManager();

        /** Get shared log. */
        @NonNull public abstract SharedLog getSharedLog();

        /** Get tethering configuration. */
        @Nullable public abstract TetheringConfiguration getTetherConfig();
    }

    @VisibleForTesting
    public BpfCoordinator(@NonNull Handler handler, @NonNull INetd netd,
            @NonNull NetworkStatsManager nsm, @NonNull SharedLog log, @NonNull Dependencies deps) {
        mHandler = handler;
        mNetd = netd;
        mLog = log.forSubComponent(TAG);
    public BpfCoordinator(@NonNull Dependencies deps) {
        mDeps = deps;
        mHandler = mDeps.getHandler();
        mNetd = mDeps.getNetd();
        mLog = mDeps.getSharedLog().forSubComponent(TAG);
        mUsingBpf = isOffloadEnabled();
        BpfTetherStatsProvider provider = new BpfTetherStatsProvider();
        try {
            nsm.registerNetworkStatsProvider(getClass().getSimpleName(), provider);
            mDeps.getNetworkStatsManager().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.
@@ -165,7 +196,6 @@ public class BpfCoordinator {
            provider = null;
        }
        mStatsProvider = provider;
        mDeps = deps;
    }

    /**
@@ -177,6 +207,11 @@ public class BpfCoordinator {
    public void startPolling() {
        if (mPollingStarted) return;

        if (!mUsingBpf) {
            mLog.i("Offload disabled");
            return;
        }

        mPollingStarted = true;
        maybeSchedulePollingStats();

@@ -211,6 +246,8 @@ public class BpfCoordinator {
     */
    public void tetherOffloadRuleAdd(
            @NonNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule) {
        if (!mUsingBpf) return;

        try {
            // TODO: Perhaps avoid to add a duplicate rule.
            mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel());
@@ -250,6 +287,8 @@ public class BpfCoordinator {
     */
    public void tetherOffloadRuleRemove(
            @NonNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule) {
        if (!mUsingBpf) return;

        try {
            // TODO: Perhaps avoid to remove a non-existent rule.
            mNetd.tetherOffloadRuleRemove(rule.toTetherOffloadRuleParcel());
@@ -293,6 +332,8 @@ public class BpfCoordinator {
     * Note that this can be only called on handler thread.
     */
    public void tetherOffloadRuleClear(@NonNull final IpServer ipServer) {
        if (!mUsingBpf) return;

        final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(
                ipServer);
        if (rules == null) return;
@@ -308,6 +349,8 @@ public class BpfCoordinator {
     * Note that this can be only called on handler thread.
     */
    public void tetherOffloadRuleUpdate(@NonNull final IpServer ipServer, int newUpstreamIfindex) {
        if (!mUsingBpf) return;

        final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(
                ipServer);
        if (rules == null) return;
@@ -330,6 +373,8 @@ public class BpfCoordinator {
     * Note that this can be only called on handler thread.
     */
    public void addUpstreamNameToLookupTable(int upstreamIfindex, @NonNull String upstreamIface) {
        if (!mUsingBpf) return;

        if (upstreamIfindex == 0 || TextUtils.isEmpty(upstreamIface)) return;

        // The same interface index to name mapping may be added by different IpServer objects or
@@ -344,6 +389,76 @@ public class BpfCoordinator {
        }
    }

    /**
     * Dump information.
     * Block the function until all the data are dumped on the handler thread or timed-out. The
     * reason is that dumpsys invokes this function on the thread of caller and the data may only
     * be allowed to be accessed on the handler thread.
     */
    public void dump(@NonNull IndentingPrintWriter pw) {
        final ConditionVariable dumpDone = new ConditionVariable();
        mHandler.post(() -> {
            pw.println("mUsingBpf: " + mUsingBpf);
            pw.println("Polling " + (mPollingStarted ? "started" : "not started"));
            pw.println("Stats provider " + (mStatsProvider != null
                    ? "registered" : "not registered"));
            pw.println("Upstream quota: " + mInterfaceQuotas.toString());

            pw.println("Forwarding stats:");
            pw.increaseIndent();
            if (mStats.size() == 0) {
                pw.println("<empty>");
            } else {
                dumpStats(pw);
            }
            pw.decreaseIndent();

            pw.println("Forwarding rules:");
            pw.increaseIndent();
            if (mIpv6ForwardingRules.size() == 0) {
                pw.println("<empty>");
            } else {
                dumpIpv6ForwardingRules(pw);
            }
            pw.decreaseIndent();

            dumpDone.open();
        });
        if (!dumpDone.block(DUMP_TIMEOUT_MS)) {
            pw.println("... dump timed-out after " + DUMP_TIMEOUT_MS + "ms");
        }
    }

    private void dumpStats(@NonNull IndentingPrintWriter pw) {
        for (int i = 0; i < mStats.size(); i++) {
            final int upstreamIfindex = mStats.keyAt(i);
            final ForwardedStats stats = mStats.get(upstreamIfindex);
            pw.println(String.format("%d(%s) - %s", upstreamIfindex, mInterfaceNames.get(
                    upstreamIfindex), stats.toString()));
        }
    }

    private void dumpIpv6ForwardingRules(@NonNull IndentingPrintWriter pw) {
        for (Map.Entry<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>> entry :
                mIpv6ForwardingRules.entrySet()) {
            IpServer ipServer = entry.getKey();
            // The rule downstream interface index is paired with the interface name from
            // IpServer#interfaceName. See #startIPv6, #updateIpv6ForwardingRules in IpServer.
            final String downstreamIface = ipServer.interfaceName();
            pw.println("[" + downstreamIface + "]: iif(iface) oif(iface) v6addr srcmac dstmac");

            pw.increaseIndent();
            LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = entry.getValue();
            for (Ipv6ForwardingRule rule : rules.values()) {
                final int upstreamIfindex = rule.upstreamIfindex;
                pw.println(String.format("%d(%s) %d(%s) %s %s %s", upstreamIfindex,
                        mInterfaceNames.get(upstreamIfindex), rule.downstreamIfindex,
                        downstreamIface, rule.address, rule.srcMac, rule.dstMac));
            }
            pw.decreaseIndent();
        }
    }

    /** IPv6 forwarding rule class. */
    public static class Ipv6ForwardingRule {
        public final int upstreamIfindex;
@@ -474,6 +589,11 @@ public class BpfCoordinator {
        }
    }

    private boolean isOffloadEnabled() {
        final TetheringConfiguration config = mDeps.getTetherConfig();
        return (config != null) ? config.enableBpfOffload : true /* default value */;
    }

    private int getInterfaceIndexFromRules(@NonNull String ifName) {
        for (LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules : mIpv6ForwardingRules
                .values()) {
+37 −2
Original line number Diff line number Diff line
@@ -63,6 +63,7 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;

import static com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE;

import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothPan;
import android.bluetooth.BluetoothProfile;
@@ -286,8 +287,6 @@ public class Tethering {
        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);
@@ -325,6 +324,37 @@ public class Tethering {
        // Load tethering configuration.
        updateConfiguration();

        // Must be initialized after tethering configuration is loaded because BpfCoordinator
        // constructor needs to use the configuration.
        mBpfCoordinator = mDeps.getBpfCoordinator(
                new BpfCoordinator.Dependencies() {
                    @NonNull
                    public Handler getHandler() {
                        return mHandler;
                    }

                    @NonNull
                    public INetd getNetd() {
                        return mNetd;
                    }

                    @NonNull
                    public NetworkStatsManager getNetworkStatsManager() {
                        return (NetworkStatsManager) mContext.getSystemService(
                            Context.NETWORK_STATS_SERVICE);
                    }

                    @NonNull
                    public SharedLog getSharedLog() {
                        return mLog;
                    }

                    @Nullable
                    public TetheringConfiguration getTetherConfig() {
                        return mConfig;
                    }
                });

        startStateMachineUpdaters();
    }

@@ -2236,6 +2266,11 @@ public class Tethering {
        mOffloadController.dump(pw);
        pw.decreaseIndent();

        pw.println("BPF offload:");
        pw.increaseIndent();
        mBpfCoordinator.dump(pw);
        pw.decreaseIndent();

        pw.println("Private address coordinator:");
        pw.increaseIndent();
        mPrivateAddressCoordinator.dump(pw);
+1 −4
Original line number Diff line number Diff line
@@ -46,11 +46,8 @@ 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);
        return new BpfCoordinator(deps);
    }

    /**
+37 −5
Original line number Diff line number Diff line
@@ -89,12 +89,14 @@ import android.os.test.TestLooper;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.networkstack.tethering.BpfCoordinator;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
import com.android.networkstack.tethering.TetheringConfiguration;

import org.junit.Before;
import org.junit.Test;
@@ -226,9 +228,36 @@ public class IpServerTest {
        when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog);
        when(mAddressCoordinator.requestDownstreamAddress(any())).thenReturn(mTestAddress);

        BpfCoordinator bc = new BpfCoordinator(new Handler(mLooper.getLooper()), mNetd,
                mStatsManager, mSharedLog, new BpfCoordinator.Dependencies());
        mBpfCoordinator = spy(bc);
        mBpfCoordinator = spy(new BpfCoordinator(
                new BpfCoordinator.Dependencies() {
                    @NonNull
                    public Handler getHandler() {
                        return new Handler(mLooper.getLooper());
                    }

                    @NonNull
                    public INetd getNetd() {
                        return mNetd;
                    }

                    @NonNull
                    public NetworkStatsManager getNetworkStatsManager() {
                        return mStatsManager;
                    }

                    @NonNull
                    public SharedLog getSharedLog() {
                        return mSharedLog;
                    }

                    @Nullable
                    public TetheringConfiguration getTetherConfig() {
                        // Returning null configuration object is a hack to enable BPF offload.
                        // See BpfCoordinator#isOffloadEnabled.
                        // TODO: Mock TetheringConfiguration to test.
                        return null;
                    }
                }));
    }

    @Test
@@ -671,18 +700,21 @@ public class IpServerTest {
        }
    }

    private TetherOffloadRuleParcel matches(
    @NonNull
    private static TetherOffloadRuleParcel matches(
            int upstreamIfindex, InetAddress dst, MacAddress dstMac) {
        return argThat(new TetherOffloadRuleParcelMatcher(upstreamIfindex, dst, dstMac));
    }

    @NonNull
    private static Ipv6ForwardingRule makeForwardingRule(
            int upstreamIfindex, @NonNull InetAddress dst, @NonNull MacAddress dstMac) {
        return new Ipv6ForwardingRule(upstreamIfindex, TEST_IFACE_PARAMS.index,
                (Inet6Address) dst, TEST_IFACE_PARAMS.macAddr, dstMac);
    }

    private TetherStatsParcel buildEmptyTetherStatsParcel(int ifIndex) {
    @NonNull
    private static TetherStatsParcel buildEmptyTetherStatsParcel(int ifIndex) {
        TetherStatsParcel parcel = new TetherStatsParcel();
        parcel.ifIndex = ifIndex;
        return parcel;
+263 −5

File changed.

Preview size limit exceeded, changes collapsed.

Loading