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

Commit db8cf2a1 authored by Benedict Wong's avatar Benedict Wong
Browse files

Pull VcnNetworkProvider out into a separate class

This change makes the VcnNetworkProvider a separate class, and caches
all NetworkRequest(s) to ensure that VcnTunnel(s) satisfy all requests
that they can accept.

Bug: 163431879
Test: atest FrameworksVcnTests
Change-Id: I3b7695628d0153a33f7e7f40d839df1463d58b07
parent 9cb58078
Loading
Loading
Loading
Loading
+4 −22
Original line number Diff line number Diff line
@@ -25,8 +25,6 @@ import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkProvider;
import android.net.NetworkRequest;
import android.net.vcn.IVcnManagementService;
import android.net.vcn.VcnConfig;
import android.os.Binder;
@@ -50,6 +48,7 @@ import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.server.vcn.TelephonySubscriptionTracker;
import com.android.server.vcn.Vcn;
import com.android.server.vcn.VcnContext;
import com.android.server.vcn.VcnNetworkProvider;
import com.android.server.vcn.util.PersistableBundleUtils;

import java.io.IOException;
@@ -390,6 +389,9 @@ public class VcnManagementService extends IVcnManagementService.Stub {
    private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
        Slog.v(TAG, "Starting VCN config for subGrp: " + subscriptionGroup);

        // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active
        //                    VCN.

        final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config);
        mVcns.put(subscriptionGroup, newInstance);
    }
@@ -493,24 +495,4 @@ public class VcnManagementService extends IVcnManagementService.Stub {
            return Collections.unmodifiableMap(mVcns);
        }
    }

    /**
     * Network provider for VCN networks.
     *
     * @hide
     */
    public class VcnNetworkProvider extends NetworkProvider {
        VcnNetworkProvider(Context context, Looper looper) {
            super(context, looper, VcnNetworkProvider.class.getSimpleName());
        }

        @Override
        public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
            synchronized (mLock) {
                for (Vcn instance : mVcns.values()) {
                    instance.onNetworkRequested(request, score, providerId);
                }
            }
        }
    }
}
+139 −10
Original line number Diff line number Diff line
@@ -16,32 +16,69 @@

package com.android.server.vcn;


import android.annotation.NonNull;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.vcn.VcnConfig;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelUuid;
import android.util.Slog;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * Represents an single instance of a VCN.
 *
 * <p>Each Vcn instance manages all tunnels for a given subscription group, including per-capability
 * networks, network selection, and multi-homing.
 * <p>Each Vcn instance manages all {@link VcnGatewayConnection}(s) for a given subscription group,
 * including per-capability networks, network selection, and multi-homing.
 *
 * @hide
 */
public class Vcn extends Handler {
    private static final String TAG = Vcn.class.getSimpleName();

    private static final int MSG_EVENT_BASE = 0;
    private static final int MSG_CMD_BASE = 100;

    /**
     * A carrier app updated the configuration.
     *
     * <p>Triggers update of config, re-evaluating all active and underlying networks.
     *
     * @param obj VcnConfig
     */
    private static final int MSG_EVENT_CONFIG_UPDATED = MSG_EVENT_BASE;

    /**
     * A NetworkRequest was added or updated.
     *
     * <p>Triggers an evaluation of all active networks, bringing up a new one if necessary.
     *
     * @param obj NetworkRequest
     */
    private static final int MSG_EVENT_NETWORK_REQUESTED = MSG_EVENT_BASE + 1;

    /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */
    private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE;

    @NonNull private final VcnContext mVcnContext;
    @NonNull private final ParcelUuid mSubscriptionGroup;
    @NonNull private final Dependencies mDeps;
    @NonNull private final VcnNetworkRequestListener mRequestListener;

    @NonNull
    private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections =
            new HashMap<>();

    @NonNull private VcnConfig mConfig;

    private boolean mIsRunning = true;

    public Vcn(
            @NonNull VcnContext vcnContext,
            @NonNull ParcelUuid subscriptionGroup,
@@ -58,31 +95,123 @@ public class Vcn extends Handler {
        mVcnContext = vcnContext;
        mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
        mDeps = Objects.requireNonNull(deps, "Missing deps");
        mRequestListener = new VcnNetworkRequestListener();

        mConfig = Objects.requireNonNull(config, "Missing config");

        // Register to receive cached and future NetworkRequests
        mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener);
    }

    /** Asynchronously updates the configuration and triggers a re-evaluation of Networks */
    public void updateConfig(@NonNull VcnConfig config) {
        Objects.requireNonNull(config, "Missing config");
        // TODO: Proxy to handler, and make config there.

        sendMessage(obtainMessage(MSG_EVENT_CONFIG_UPDATED, config));
    }

    /** Asynchronously tears down this Vcn instance, along with all tunnels and Networks */
    /** Asynchronously tears down this Vcn instance, including VcnGatewayConnection(s) */
    public void teardownAsynchronously() {
        // TODO: Proxy to handler, and teardown there.
        sendMessageAtFrontOfQueue(obtainMessage(MSG_CMD_TEARDOWN));
    }

    /** Notifies this Vcn instance of a new NetworkRequest */
    private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener {
        @Override
        public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
            Objects.requireNonNull(request, "Missing request");

        // TODO: Proxy to handler, and handle there.
            sendMessage(obtainMessage(MSG_EVENT_NETWORK_REQUESTED, score, providerId, request));
        }
    }

    @Override
    public void handleMessage(@NonNull Message msg) {
        // TODO: Do something
        if (!mIsRunning) {
            return;
        }

        switch (msg.what) {
            case MSG_EVENT_CONFIG_UPDATED:
                handleConfigUpdated((VcnConfig) msg.obj);
                break;
            case MSG_EVENT_NETWORK_REQUESTED:
                handleNetworkRequested((NetworkRequest) msg.obj, msg.arg1, msg.arg2);
                break;
            case MSG_CMD_TEARDOWN:
                handleTeardown();
                break;
            default:
                Slog.wtf(getLogTag(), "Unknown msg.what: " + msg.what);
        }
    }

    private void handleConfigUpdated(@NonNull VcnConfig config) {
        // TODO: Add a dump function in VcnConfig that omits PII. Until then, use hashCode()
        Slog.v(getLogTag(), String.format("Config updated: config = %s", config.hashCode()));

        mConfig = config;

        // TODO: Reevaluate active VcnGatewayConnection(s)
    }

    private void handleTeardown() {
        mVcnContext.getVcnNetworkProvider().unregisterListener(mRequestListener);

        for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
            gatewayConnection.teardownAsynchronously();
        }

        mIsRunning = false;
    }

    private void handleNetworkRequested(
            @NonNull NetworkRequest request, int score, int providerId) {
        if (score > getNetworkScore()) {
            Slog.v(getLogTag(),
                    "Request " + request.requestId + " already satisfied by higher-scoring ("
                            + score + ") network from provider " + providerId);
            return;
        }

        // If preexisting VcnGatewayConnection(s) satisfy request, return
        for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) {
            if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
                Slog.v(getLogTag(),
                        "Request " + request.requestId
                                + " satisfied by existing VcnGatewayConnection");
                return;
            }
        }

        // If any supported (but not running) VcnGatewayConnection(s) can satisfy request, bring it
        // up
        for (VcnGatewayConnectionConfig gatewayConnectionConfig :
                mConfig.getGatewayConnectionConfigs()) {
            if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
                Slog.v(
                        getLogTag(),
                        "Bringing up new VcnGatewayConnection for request " + request.requestId);

                final VcnGatewayConnection vcnGatewayConnection =
                        new VcnGatewayConnection(
                                mVcnContext, mSubscriptionGroup, gatewayConnectionConfig);
                mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection);
            }
        }
    }

    private boolean requestSatisfiedByGatewayConnectionConfig(
            @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) {
        final NetworkCapabilities configCaps = new NetworkCapabilities();
        for (int cap : config.getAllExposedCapabilities()) {
            configCaps.addCapability(cap);
        }

        return request.networkCapabilities.satisfiedByNetworkCapabilities(configCaps);
    }

    private String getLogTag() {
        return String.format("%s [%d]", TAG, mSubscriptionGroup.hashCode());
    }

    /** Retrieves the network score for a VCN Network */
+0 −2
Original line number Diff line number Diff line
@@ -20,8 +20,6 @@ import android.annotation.NonNull;
import android.content.Context;
import android.os.Looper;

import com.android.server.VcnManagementService.VcnNetworkProvider;

import java.util.Objects;

/**
+2 −2
Original line number Diff line number Diff line
@@ -65,8 +65,8 @@ public class VcnGatewayConnection extends Handler implements UnderlyingNetworkTr
                mDeps.newUnderlyingNetworkTracker(mVcnContext, subscriptionGroup, this);
    }

    /** Tears down this GatewayConnection, and any resources used */
    public void teardown() {
    /** Asynchronously tears down this GatewayConnection, and any resources used */
    public void teardownAsynchronously() {
        mUnderlyingNetworkTracker.teardown();
    }

+108 −0
Original line number Diff line number Diff line
/*
 * 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.server.vcn;

import android.annotation.NonNull;
import android.content.Context;
import android.net.NetworkProvider;
import android.net.NetworkRequest;
import android.os.Looper;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;

import java.util.Objects;
import java.util.Set;

/**
 * VCN Network Provider routes NetworkRequests to listeners to bring up tunnels as needed.
 *
 * <p>The VcnNetworkProvider provides a caching layer to ensure that all listeners receive all
 * active NetworkRequest(s), including ones that were filed prior to listener registration.
 *
 * @hide
 */
public class VcnNetworkProvider extends NetworkProvider {
    private static final String TAG = VcnNetworkProvider.class.getSimpleName();

    private final Set<NetworkRequestListener> mListeners = new ArraySet<>();
    private final SparseArray<NetworkRequestEntry> mRequests = new SparseArray<>();

    public VcnNetworkProvider(Context context, Looper looper) {
        super(context, looper, VcnNetworkProvider.class.getSimpleName());
    }

    // Package-private
    void registerListener(@NonNull NetworkRequestListener listener) {
        mListeners.add(listener);

        // Send listener all cached requests
        for (int i = 0; i < mRequests.size(); i++) {
            notifyListenerForEvent(listener, mRequests.valueAt(i));
        }
    }

    // Package-private
    void unregisterListener(@NonNull NetworkRequestListener listener) {
        mListeners.remove(listener);
    }

    private void notifyListenerForEvent(
            @NonNull NetworkRequestListener listener, @NonNull NetworkRequestEntry entry) {
        listener.onNetworkRequested(entry.mRequest, entry.mScore, entry.mProviderId);
    }

    @Override
    public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
        Slog.v(
                TAG,
                String.format(
                        "Network requested: Request = %s, score = %d, providerId = %d",
                        request, score, providerId));

        final NetworkRequestEntry entry = new NetworkRequestEntry(request, score, providerId);
        mRequests.put(request.requestId, entry);

        // TODO(b/176939047): Intelligently route requests to prioritized VcnInstances (based on
        // Default Data Sub, or similar)
        for (NetworkRequestListener listener : mListeners) {
            notifyListenerForEvent(listener, entry);
        }
    }

    @Override
    public void onNetworkRequestWithdrawn(@NonNull NetworkRequest request) {
        mRequests.remove(request.requestId);
    }

    private static class NetworkRequestEntry {
        public final NetworkRequest mRequest;
        public final int mScore;
        public final int mProviderId;

        private NetworkRequestEntry(@NonNull NetworkRequest request, int score, int providerId) {
            mRequest = Objects.requireNonNull(request, "Missing request");
            mScore = score;
            mProviderId = providerId;
        }
    }

    // package-private
    interface NetworkRequestListener {
        void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId);
    }
}
Loading