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

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

Revert "Revert "Take all VPN underlying networks into account when migrating traffic for""

This reverts commit d8220c20.

Reason for revert: Fix available for deadlocks.

Bug: 134244752
Change-Id: Ib65214598837289bd39dbf040b56ab7835f893ba
parent 781ac9ef
Loading
Loading
Loading
Loading
+207 −115
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.net;

import static android.os.Process.CLAT_UID;

import android.annotation.NonNull;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -1175,133 +1176,217 @@ public class NetworkStats implements Parcelable {
    /**
     * VPN accounting. Move some VPN's underlying traffic to other UIDs that use tun0 iface.
     *
     * This method should only be called on delta NetworkStats. Do not call this method on a
     * snapshot {@link NetworkStats} object because the tunUid and/or the underlyingIface may
     * change over time.
     * <p>This method should only be called on delta NetworkStats. Do not call this method on a
     * snapshot {@link NetworkStats} object because the tunUid and/or the underlyingIface may change
     * over time.
     *
     * This method performs adjustments for one active VPN package and one VPN iface at a time.
     *
     * It is possible for the VPN software to use multiple underlying networks. This method
     * only migrates traffic for the primary underlying network.
     * <p>This method performs adjustments for one active VPN package and one VPN iface at a time.
     *
     * @param tunUid uid of the VPN application
     * @param tunIface iface of the vpn tunnel
     * @param underlyingIface the primary underlying network iface used by the VPN application
     * @return true if it successfully adjusts the accounting for VPN, false otherwise
     * @param underlyingIfaces underlying network ifaces used by the VPN application
     */
    public boolean migrateTun(int tunUid, String tunIface, String underlyingIface) {
        Entry tunIfaceTotal = new Entry();
        Entry underlyingIfaceTotal = new Entry();

        tunAdjustmentInit(tunUid, tunIface, underlyingIface, tunIfaceTotal, underlyingIfaceTotal);
    public void migrateTun(int tunUid, @NonNull String tunIface,
            @NonNull String[] underlyingIfaces) {
        // Combined usage by all apps using VPN.
        final Entry tunIfaceTotal = new Entry();
        // Usage by VPN, grouped by its {@code underlyingIfaces}.
        final Entry[] perInterfaceTotal = new Entry[underlyingIfaces.length];
        // Usage by VPN, summed across all its {@code underlyingIfaces}.
        final Entry underlyingIfacesTotal = new Entry();

        // If tunIface < underlyingIface, it leaves the overhead traffic in the VPN app.
        // If tunIface > underlyingIface, the VPN app doesn't get credit for data compression.
        // Negative stats should be avoided.
        Entry pool = tunGetPool(tunIfaceTotal, underlyingIfaceTotal);
        if (pool.isEmpty()) {
            return true;
        for (int i = 0; i < perInterfaceTotal.length; i++) {
            perInterfaceTotal[i] = new Entry();
        }
        Entry moved =
                addTrafficToApplications(tunUid, tunIface, underlyingIface, tunIfaceTotal, pool);
        deductTrafficFromVpnApp(tunUid, underlyingIface, moved);

        if (!moved.isEmpty()) {
            Slog.wtf(TAG, "Failed to deduct underlying network traffic from VPN package. Moved="
                    + moved);
            return false;
        }
        return true;
        tunAdjustmentInit(tunUid, tunIface, underlyingIfaces, tunIfaceTotal, perInterfaceTotal,
                underlyingIfacesTotal);

        // If tunIface < underlyingIfacesTotal, it leaves the overhead traffic in the VPN app.
        // If tunIface > underlyingIfacesTotal, the VPN app doesn't get credit for data compression.
        // Negative stats should be avoided.
        final Entry[] moved =
                addTrafficToApplications(tunUid, tunIface, underlyingIfaces, tunIfaceTotal,
                        perInterfaceTotal, underlyingIfacesTotal);
        deductTrafficFromVpnApp(tunUid, underlyingIfaces, moved);
    }

    /**
     * Initializes the data used by the migrateTun() method.
     *
     * This is the first pass iteration which does the following work:
     * (1) Adds up all the traffic through the tunUid's underlyingIface
     *     (both foreground and background).
     * (2) Adds up all the traffic through tun0 excluding traffic from the vpn app itself.
     * <p>This is the first pass iteration which does the following work:
     *
     * <ul>
     *   <li>Adds up all the traffic through the tunUid's underlyingIfaces (both foreground and
     *       background).
     *   <li>Adds up all the traffic through tun0 excluding traffic from the vpn app itself.
     * </ul>
     *
     * @param tunUid uid of the VPN application
     * @param tunIface iface of the vpn tunnel
     * @param underlyingIfaces underlying network ifaces used by the VPN application
     * @param tunIfaceTotal output parameter; combined data usage by all apps using VPN
     * @param perInterfaceTotal output parameter; data usage by VPN app, grouped by its {@code
     *     underlyingIfaces}
     * @param underlyingIfacesTotal output parameter; data usage by VPN, summed across all of its
     *     {@code underlyingIfaces}
     */
    private void tunAdjustmentInit(int tunUid, String tunIface, String underlyingIface,
            Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
        Entry recycle = new Entry();
    private void tunAdjustmentInit(int tunUid, @NonNull String tunIface,
            @NonNull String[] underlyingIfaces, @NonNull Entry tunIfaceTotal,
            @NonNull Entry[] perInterfaceTotal, @NonNull Entry underlyingIfacesTotal) {
        final Entry recycle = new Entry();
        for (int i = 0; i < size; i++) {
            getValues(i, recycle);
            if (recycle.uid == UID_ALL) {
                throw new IllegalStateException(
                        "Cannot adjust VPN accounting on an iface aggregated NetworkStats.");
            } if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) {
            }
            if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) {
                throw new IllegalStateException(
                        "Cannot adjust VPN accounting on a NetworkStats containing SET_DBG_VPN_*");
            }

            if (recycle.uid == tunUid && recycle.tag == TAG_NONE
                    && Objects.equals(underlyingIface, recycle.iface)) {
                underlyingIfaceTotal.add(recycle);
            if (recycle.tag != TAG_NONE) {
                // TODO(b/123666283): Take all tags for tunUid into account.
                continue;
            }

            if (recycle.uid != tunUid && recycle.tag == TAG_NONE
                    && Objects.equals(tunIface, recycle.iface)) {
            if (recycle.uid == tunUid) {
                // Add up traffic through tunUid's underlying interfaces.
                for (int j = 0; j < underlyingIfaces.length; j++) {
                    if (Objects.equals(underlyingIfaces[j], recycle.iface)) {
                        perInterfaceTotal[j].add(recycle);
                        underlyingIfacesTotal.add(recycle);
                        break;
                    }
                }
            } else if (tunIface.equals(recycle.iface)) {
                // Add up all tunIface traffic excluding traffic from the vpn app itself.
                tunIfaceTotal.add(recycle);
            }
        }
    }

    private static Entry tunGetPool(Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
        Entry pool = new Entry();
        pool.rxBytes = Math.min(tunIfaceTotal.rxBytes, underlyingIfaceTotal.rxBytes);
        pool.rxPackets = Math.min(tunIfaceTotal.rxPackets, underlyingIfaceTotal.rxPackets);
        pool.txBytes = Math.min(tunIfaceTotal.txBytes, underlyingIfaceTotal.txBytes);
        pool.txPackets = Math.min(tunIfaceTotal.txPackets, underlyingIfaceTotal.txPackets);
        pool.operations = Math.min(tunIfaceTotal.operations, underlyingIfaceTotal.operations);
        return pool;
    /**
     * Distributes traffic across apps that are using given {@code tunIface}, and returns the total
     * traffic that should be moved off of {@code tunUid} grouped by {@code underlyingIfaces}.
     *
     * @param tunUid uid of the VPN application
     * @param tunIface iface of the vpn tunnel
     * @param underlyingIfaces underlying network ifaces used by the VPN application
     * @param tunIfaceTotal combined data usage across all apps using {@code tunIface}
     * @param perInterfaceTotal data usage by VPN app, grouped by its {@code underlyingIfaces}
     * @param underlyingIfacesTotal data usage by VPN, summed across all of its {@code
     *     underlyingIfaces}
     */
    private Entry[] addTrafficToApplications(int tunUid, @NonNull String tunIface,
            @NonNull String[] underlyingIfaces, @NonNull Entry tunIfaceTotal,
            @NonNull Entry[] perInterfaceTotal, @NonNull Entry underlyingIfacesTotal) {
        // Traffic that should be moved off of each underlying interface for tunUid (see
        // deductTrafficFromVpnApp below).
        final Entry[] moved = new Entry[underlyingIfaces.length];
        for (int i = 0; i < underlyingIfaces.length; i++) {
            moved[i] = new Entry();
        }

    private Entry addTrafficToApplications(int tunUid, String tunIface, String underlyingIface,
            Entry tunIfaceTotal, Entry pool) {
        Entry moved = new Entry();
        Entry tmpEntry = new Entry();
        tmpEntry.iface = underlyingIface;
        final Entry tmpEntry = new Entry();
        for (int i = 0; i < size; i++) {
            // the vpn app is excluded from the redistribution but all moved traffic will be
            // deducted from the vpn app (see deductTrafficFromVpnApp below).
            if (Objects.equals(iface[i], tunIface) && uid[i] != tunUid) {
            if (!Objects.equals(iface[i], tunIface)) {
                // Consider only entries that go onto the VPN interface.
                continue;
            }
            if (uid[i] == tunUid) {
                // Exclude VPN app from the redistribution, as it can choose to create packet
                // streams by writing to itself.
                continue;
            }
            tmpEntry.uid = uid[i];
            tmpEntry.tag = tag[i];
            tmpEntry.metered = metered[i];
            tmpEntry.roaming = roaming[i];
            tmpEntry.defaultNetwork = defaultNetwork[i];

            // In a first pass, compute each UID's total share of data across all underlyingIfaces.
            // This is computed on the basis of the share of each UID's usage over tunIface.
            // TODO: Consider refactoring first pass into a separate helper method.
            long totalRxBytes = 0;
            if (tunIfaceTotal.rxBytes > 0) {
                    tmpEntry.rxBytes = pool.rxBytes * rxBytes[i] / tunIfaceTotal.rxBytes;
                } else {
                // Note - The multiplication below should not overflow since NetworkStatsService
                // processes this every time device has transmitted/received amount equivalent to
                // global threshold alert (~ 2MB) across all interfaces.
                final long rxBytesAcrossUnderlyingIfaces =
                        underlyingIfacesTotal.rxBytes * rxBytes[i] / tunIfaceTotal.rxBytes;
                // app must not be blamed for more than it consumed on tunIface
                totalRxBytes = Math.min(rxBytes[i], rxBytesAcrossUnderlyingIfaces);
            }
            long totalRxPackets = 0;
            if (tunIfaceTotal.rxPackets > 0) {
                final long rxPacketsAcrossUnderlyingIfaces =
                        underlyingIfacesTotal.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets;
                totalRxPackets = Math.min(rxPackets[i], rxPacketsAcrossUnderlyingIfaces);
            }
            long totalTxBytes = 0;
            if (tunIfaceTotal.txBytes > 0) {
                final long txBytesAcrossUnderlyingIfaces =
                        underlyingIfacesTotal.txBytes * txBytes[i] / tunIfaceTotal.txBytes;
                totalTxBytes = Math.min(txBytes[i], txBytesAcrossUnderlyingIfaces);
            }
            long totalTxPackets = 0;
            if (tunIfaceTotal.txPackets > 0) {
                final long txPacketsAcrossUnderlyingIfaces =
                        underlyingIfacesTotal.txPackets * txPackets[i] / tunIfaceTotal.txPackets;
                totalTxPackets = Math.min(txPackets[i], txPacketsAcrossUnderlyingIfaces);
            }
            long totalOperations = 0;
            if (tunIfaceTotal.operations > 0) {
                final long operationsAcrossUnderlyingIfaces =
                        underlyingIfacesTotal.operations * operations[i] / tunIfaceTotal.operations;
                totalOperations = Math.min(operations[i], operationsAcrossUnderlyingIfaces);
            }
            // In a second pass, distribute these values across interfaces in the proportion that
            // each interface represents of the total traffic of the underlying interfaces.
            for (int j = 0; j < underlyingIfaces.length; j++) {
                tmpEntry.iface = underlyingIfaces[j];
                tmpEntry.rxBytes = 0;
                // Reset 'set' to correct value since it gets updated when adding debug info below.
                tmpEntry.set = set[i];
                if (underlyingIfacesTotal.rxBytes > 0) {
                    tmpEntry.rxBytes =
                            totalRxBytes
                                    * perInterfaceTotal[j].rxBytes
                                    / underlyingIfacesTotal.rxBytes;
                }
                if (tunIfaceTotal.rxPackets > 0) {
                    tmpEntry.rxPackets = pool.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets;
                } else {
                tmpEntry.rxPackets = 0;
                if (underlyingIfacesTotal.rxPackets > 0) {
                    tmpEntry.rxPackets =
                            totalRxPackets
                                    * perInterfaceTotal[j].rxPackets
                                    / underlyingIfacesTotal.rxPackets;
                }
                if (tunIfaceTotal.txBytes > 0) {
                    tmpEntry.txBytes = pool.txBytes * txBytes[i] / tunIfaceTotal.txBytes;
                } else {
                tmpEntry.txBytes = 0;
                if (underlyingIfacesTotal.txBytes > 0) {
                    tmpEntry.txBytes =
                            totalTxBytes
                                    * perInterfaceTotal[j].txBytes
                                    / underlyingIfacesTotal.txBytes;
                }
                if (tunIfaceTotal.txPackets > 0) {
                    tmpEntry.txPackets = pool.txPackets * txPackets[i] / tunIfaceTotal.txPackets;
                } else {
                tmpEntry.txPackets = 0;
                if (underlyingIfacesTotal.txPackets > 0) {
                    tmpEntry.txPackets =
                            totalTxPackets
                                    * perInterfaceTotal[j].txPackets
                                    / underlyingIfacesTotal.txPackets;
                }
                if (tunIfaceTotal.operations > 0) {
                    tmpEntry.operations =
                            pool.operations * operations[i] / tunIfaceTotal.operations;
                } else {
                tmpEntry.operations = 0;
                if (underlyingIfacesTotal.operations > 0) {
                    tmpEntry.operations =
                            totalOperations
                                    * perInterfaceTotal[j].operations
                                    / underlyingIfacesTotal.operations;
                }
                tmpEntry.uid = uid[i];
                tmpEntry.tag = tag[i];
                tmpEntry.set = set[i];
                tmpEntry.metered = metered[i];
                tmpEntry.roaming = roaming[i];
                tmpEntry.defaultNetwork = defaultNetwork[i];

                combineValues(tmpEntry);
                if (tag[i] == TAG_NONE) {
                    moved.add(tmpEntry);
                    moved[j].add(tmpEntry);
                    // Add debug info
                    tmpEntry.set = SET_DBG_VPN_IN;
                    combineValues(tmpEntry);
@@ -1311,38 +1396,45 @@ public class NetworkStats implements Parcelable {
        return moved;
    }

    private void deductTrafficFromVpnApp(int tunUid, String underlyingIface, Entry moved) {
    private void deductTrafficFromVpnApp(
            int tunUid,
            @NonNull String[] underlyingIfaces,
            @NonNull Entry[] moved) {
        for (int i = 0; i < underlyingIfaces.length; i++) {
            // Add debug info
        moved.uid = tunUid;
        moved.set = SET_DBG_VPN_OUT;
        moved.tag = TAG_NONE;
        moved.iface = underlyingIface;
        moved.metered = METERED_ALL;
        moved.roaming = ROAMING_ALL;
        moved.defaultNetwork = DEFAULT_NETWORK_ALL;
        combineValues(moved);
            moved[i].uid = tunUid;
            moved[i].set = SET_DBG_VPN_OUT;
            moved[i].tag = TAG_NONE;
            moved[i].iface = underlyingIfaces[i];
            moved[i].metered = METERED_ALL;
            moved[i].roaming = ROAMING_ALL;
            moved[i].defaultNetwork = DEFAULT_NETWORK_ALL;
            combineValues(moved[i]);

            // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than
            // the TAG_NONE traffic.
            //
        // Relies on the fact that the underlying traffic only has state ROAMING_NO and METERED_NO,
        // which should be the case as it comes directly from the /proc file. We only blend in the
        // roaming data after applying these adjustments, by checking the NetworkIdentity of the
        // underlying iface.
        int idxVpnBackground = findIndex(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE,
                METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
            // Relies on the fact that the underlying traffic only has state ROAMING_NO and
            // METERED_NO, which should be the case as it comes directly from the /proc file.
            // We only blend in the roaming data after applying these adjustments, by checking the
            // NetworkIdentity of the underlying iface.
            final int idxVpnBackground = findIndex(underlyingIfaces[i], tunUid, SET_DEFAULT,
                            TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
            if (idxVpnBackground != -1) {
            tunSubtract(idxVpnBackground, this, moved);
                // Note - tunSubtract also updates moved[i]; whatever traffic that's left is removed
                // from foreground usage.
                tunSubtract(idxVpnBackground, this, moved[i]);
            }

        int idxVpnForeground = findIndex(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE,
                METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
            final int idxVpnForeground = findIndex(underlyingIfaces[i], tunUid, SET_FOREGROUND,
                            TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
            if (idxVpnForeground != -1) {
            tunSubtract(idxVpnForeground, this, moved);
                tunSubtract(idxVpnForeground, this, moved[i]);
            }
        }
    }

    private static void tunSubtract(int i, NetworkStats left, Entry right) {
    private static void tunSubtract(int i, @NonNull NetworkStats left, @NonNull Entry right) {
        long rxBytes = Math.min(left.rxBytes[i], right.rxBytes);
        left.rxBytes[i] -= rxBytes;
        right.rxBytes -= rxBytes;
+6 −4
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.internal.net;
import android.os.Parcel;
import android.os.Parcelable;

import java.util.Arrays;

/**
 * A lightweight container used to carry information of the ongoing VPN.
 * Internal use only..
@@ -28,14 +30,14 @@ import android.os.Parcelable;
public class VpnInfo implements Parcelable {
    public int ownerUid;
    public String vpnIface;
    public String primaryUnderlyingIface;
    public String[] underlyingIfaces;

    @Override
    public String toString() {
        return "VpnInfo{"
                + "ownerUid=" + ownerUid
                + ", vpnIface='" + vpnIface + '\''
                + ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\''
                + ", underlyingIfaces='" + Arrays.toString(underlyingIfaces) + '\''
                + '}';
    }

@@ -48,7 +50,7 @@ public class VpnInfo implements Parcelable {
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(ownerUid);
        dest.writeString(vpnIface);
        dest.writeString(primaryUnderlyingIface);
        dest.writeStringArray(underlyingIfaces);
    }

    public static final Parcelable.Creator<VpnInfo> CREATOR = new Parcelable.Creator<VpnInfo>() {
@@ -57,7 +59,7 @@ public class VpnInfo implements Parcelable {
            VpnInfo info = new VpnInfo();
            info.ownerUid = source.readInt();
            info.vpnIface = source.readString();
            info.primaryUnderlyingIface = source.readString();
            info.underlyingIfaces = source.readStringArray();
            return info;
        }

+41 −13
Original line number Diff line number Diff line
@@ -19,13 +19,22 @@ package android.net;
import com.google.caliper.BeforeExperiment;
import com.google.caliper.Param;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class NetworkStatsBenchmark {
    private static final String UNDERLYING_IFACE = "wlan0";
    private static final String[] UNDERLYING_IFACES = {"wlan0", "rmnet0"};
    private static final String TUN_IFACE = "tun0";
    private static final int TUN_UID = 999999999;

    @Param({"100", "1000"})
    private int mSize;
    /**
     * Should not be more than the length of {@link #UNDERLYING_IFACES}.
     */
    @Param({"1", "2"})
    private int mNumUnderlyingIfaces;
    private NetworkStats mNetworkStats;

    @BeforeExperiment
@@ -33,8 +42,10 @@ public class NetworkStatsBenchmark {
        mNetworkStats = new NetworkStats(0, mSize + 2);
        int uid = 0;
        NetworkStats.Entry recycle = new NetworkStats.Entry();
        final List<String> allIfaces = getAllIfacesForBenchmark(); // also contains TUN_IFACE.
        final int totalIfaces = allIfaces.size();
        for (int i = 0; i < mSize; i++) {
            recycle.iface = (i < mSize / 2) ? TUN_IFACE : UNDERLYING_IFACE;
            recycle.iface = allIfaces.get(i % totalIfaces);
            recycle.uid = uid;
            recycle.set = i % 2;
            recycle.tag = NetworkStats.TAG_NONE;
@@ -48,7 +59,9 @@ public class NetworkStatsBenchmark {
                uid++;
            }
        }
        recycle.iface = UNDERLYING_IFACE;

        for (int i = 0; i < mNumUnderlyingIfaces; i++) {
            recycle.iface = UNDERLYING_IFACES[i];
            recycle.uid = TUN_UID;
            recycle.set = NetworkStats.SET_FOREGROUND;
            recycle.tag = NetworkStats.TAG_NONE;
@@ -59,11 +72,26 @@ public class NetworkStatsBenchmark {
            recycle.operations = 0;
            mNetworkStats.addValues(recycle);
        }
    }

    private String[] getVpnUnderlyingIfaces() {
        return Arrays.copyOf(UNDERLYING_IFACES, mNumUnderlyingIfaces);
    }

    /**
     * Same as {@link #getVpnUnderlyingIfaces}, but also contains {@link #TUN_IFACE}.
     */
    private List<String> getAllIfacesForBenchmark() {
        List<String> ifaces = new ArrayList<>();
        ifaces.add(TUN_IFACE);
        ifaces.addAll(Arrays.asList(getVpnUnderlyingIfaces()));
        return ifaces;
    }

    public void timeMigrateTun(int reps) {
        for (int i = 0; i < reps; i++) {
            NetworkStats stats = mNetworkStats.clone();
            stats.migrateTun(TUN_UID, TUN_IFACE, UNDERLYING_IFACE);
            stats.migrateTun(TUN_UID, TUN_IFACE, getVpnUnderlyingIfaces());
        }
    }

+16 −9
Original line number Diff line number Diff line
@@ -4374,7 +4374,7 @@ public class ConnectivityService extends IConnectivityManager.Stub

    /**
     * @return VPN information for accounting, or null if we can't retrieve all required
     *         information, e.g primary underlying iface.
     *         information, e.g underlying ifaces.
     */
    @Nullable
    private VpnInfo createVpnInfo(Vpn vpn) {
@@ -4386,17 +4386,24 @@ public class ConnectivityService extends IConnectivityManager.Stub
        // see VpnService.setUnderlyingNetworks()'s javadoc about how to interpret
        // the underlyingNetworks list.
        if (underlyingNetworks == null) {
            NetworkAgentInfo defaultNetwork = getDefaultNetwork();
            if (defaultNetwork != null && defaultNetwork.linkProperties != null) {
                info.primaryUnderlyingIface = getDefaultNetwork().linkProperties.getInterfaceName();
            NetworkAgentInfo defaultNai = getDefaultNetwork();
            if (defaultNai != null && defaultNai.linkProperties != null) {
                underlyingNetworks = new Network[] { defaultNai.network };
            }
        }
        if (underlyingNetworks != null && underlyingNetworks.length > 0) {
            List<String> interfaces = new ArrayList<>();
            for (Network network : underlyingNetworks) {
                LinkProperties lp = getLinkProperties(network);
                if (lp != null) {
                    interfaces.add(lp.getInterfaceName());
                }
            }
        } else if (underlyingNetworks.length > 0) {
            LinkProperties linkProperties = getLinkProperties(underlyingNetworks[0]);
            if (linkProperties != null) {
                info.primaryUnderlyingIface = linkProperties.getInterfaceName();
            if (!interfaces.isEmpty()) {
                info.underlyingIfaces = interfaces.toArray(new String[interfaces.size()]);
            }
        }
        return info.primaryUnderlyingIface == null ? null : info;
        return info.underlyingIfaces == null ? null : info;
    }

    /**
+3 −3

File changed.

Preview size limit exceeded, changes collapsed.

Loading