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

Commit 0d6ffbad authored by Erik Kline's avatar Erik Kline Committed by android-build-merger
Browse files

Merge "Merge IPv6TetheringInterfaceServices into TetherInterfaceStateMachine"...

Merge "Merge IPv6TetheringInterfaceServices into TetherInterfaceStateMachine" am: 6f029bbc am: 35728f01 am: d2bf55e3
am: 607df03a

Change-Id: Ib6e6356e6caa469523fc098e850108de97b494e2
parents 1e03f56d 607df03a
Loading
Loading
Loading
Loading
+1 −3
Original line number Diff line number Diff line
@@ -88,7 +88,6 @@ import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.server.connectivity.tethering.IControlsTethering;
import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
import com.android.server.connectivity.tethering.IPv6TetheringInterfaceServices;
import com.android.server.connectivity.tethering.OffloadController;
import com.android.server.connectivity.tethering.SimChangeListener;
import com.android.server.connectivity.tethering.TetherInterfaceStateMachine;
@@ -1814,8 +1813,7 @@ public class Tethering extends BaseNetworkObserver {
        final TetherState tetherState = new TetherState(
                new TetherInterfaceStateMachine(
                    iface, mLooper, interfaceType, mLog, mNMService, mStatsService,
                    makeControlCallback(iface),
                    new IPv6TetheringInterfaceServices(iface, mNMService, mLog)));
                    makeControlCallback(iface)));
        mTetherStates.put(iface, tetherState);
        tetherState.stateMachine.start();
    }
+0 −299
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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.connectivity.tethering;

import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;

import android.net.INetd;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
import android.net.NetworkState;
import android.net.RouteInfo;
import android.net.ip.RouterAdvertisementDaemon;
import android.net.ip.RouterAdvertisementDaemon.RaParams;
import android.net.util.NetdService;
import android.net.util.SharedLog;
import android.os.INetworkManagementService;
import android.os.ServiceSpecificException;
import android.os.RemoteException;
import android.util.Slog;

import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Objects;
import java.util.Random;


/**
 * @hide
 */
public class IPv6TetheringInterfaceServices {
    private static final String TAG = IPv6TetheringInterfaceServices.class.getSimpleName();
    private static final IpPrefix LINK_LOCAL_PREFIX = new IpPrefix("fe80::/64");

    private final String mIfName;
    private final INetworkManagementService mNMService;
    private final SharedLog mLog;

    private NetworkInterface mNetworkInterface;
    private byte[] mHwAddr;
    private LinkProperties mLastIPv6LinkProperties;
    private RouterAdvertisementDaemon mRaDaemon;
    private RaParams mLastRaParams;

    public IPv6TetheringInterfaceServices(
            String ifname, INetworkManagementService nms, SharedLog log) {
        mIfName = ifname;
        mNMService = nms;
        mLog = log.forSubComponent(mIfName);
    }

    public boolean start() {
        // TODO: Refactor for testability (perhaps passing an android.system.Os
        // instance and calling getifaddrs() directly).
        try {
            mNetworkInterface = NetworkInterface.getByName(mIfName);
        } catch (SocketException e) {
            mLog.e("Error looking up NetworkInterfaces: " + e);
            stop();
            return false;
        }
        if (mNetworkInterface == null) {
            mLog.e("Failed to find NetworkInterface");
            stop();
            return false;
        }

        try {
            mHwAddr = mNetworkInterface.getHardwareAddress();
        } catch (SocketException e) {
            mLog.e("Failed to find hardware address: " + e);
            stop();
            return false;
        }

        final int ifindex = mNetworkInterface.getIndex();
        mRaDaemon = new RouterAdvertisementDaemon(mIfName, ifindex, mHwAddr);
        if (!mRaDaemon.start()) {
            stop();
            return false;
        }

        return true;
    }

    public void stop() {
        mNetworkInterface = null;
        mHwAddr = null;
        setRaParams(null);

        if (mRaDaemon != null) {
            mRaDaemon.stop();
            mRaDaemon = null;
        }
    }

    // IPv6TetheringCoordinator sends updates with carefully curated IPv6-only
    // LinkProperties. These have extraneous data filtered out and only the
    // necessary prefixes included (per its prefix distribution policy).
    //
    // TODO: Evaluate using a data structure than is more directly suited to
    // communicating only the relevant information.
    public void updateUpstreamIPv6LinkProperties(LinkProperties v6only) {
        if (mRaDaemon == null) return;

        // Avoid unnecessary work on spurious updates.
        if (Objects.equals(mLastIPv6LinkProperties, v6only)) {
            return;
        }

        RaParams params = null;

        if (v6only != null) {
            params = new RaParams();
            params.mtu = v6only.getMtu();
            params.hasDefaultRoute = v6only.hasIPv6DefaultRoute();

            for (LinkAddress linkAddr : v6only.getLinkAddresses()) {
                if (linkAddr.getPrefixLength() != RFC7421_PREFIX_LENGTH) continue;

                final IpPrefix prefix = new IpPrefix(
                        linkAddr.getAddress(), linkAddr.getPrefixLength());
                params.prefixes.add(prefix);

                final Inet6Address dnsServer = getLocalDnsIpFor(prefix);
                if (dnsServer != null) {
                    params.dnses.add(dnsServer);
                }
            }
        }
        // If v6only is null, we pass in null to setRaParams(), which handles
        // deprecation of any existing RA data.

        setRaParams(params);
        mLastIPv6LinkProperties = v6only;
    }


    private void configureLocalRoutes(
            HashSet<IpPrefix> deprecatedPrefixes, HashSet<IpPrefix> newPrefixes) {
        // [1] Remove the routes that are deprecated.
        if (!deprecatedPrefixes.isEmpty()) {
            final ArrayList<RouteInfo> toBeRemoved = getLocalRoutesFor(deprecatedPrefixes);
            try {
                final int removalFailures = mNMService.removeRoutesFromLocalNetwork(toBeRemoved);
                if (removalFailures > 0) {
                    mLog.e(String.format("Failed to remove %d IPv6 routes from local table.",
                            removalFailures));
                }
            } catch (RemoteException e) {
                mLog.e("Failed to remove IPv6 routes from local table: " + e);
            }
        }

        // [2] Add only the routes that have not previously been added.
        if (newPrefixes != null && !newPrefixes.isEmpty()) {
            HashSet<IpPrefix> addedPrefixes = (HashSet) newPrefixes.clone();
            if (mLastRaParams != null) {
                addedPrefixes.removeAll(mLastRaParams.prefixes);
            }

            if (mLastRaParams == null || mLastRaParams.prefixes.isEmpty()) {
                // We need to be able to send unicast RAs, and clients might
                // like to ping the default router's link-local address.  Note
                // that we never remove the link-local route from the network
                // until Tethering disables tethering on the interface. We
                // only need to add the link-local prefix once, but in the
                // event we add it more than once netd silently ignores EEXIST.
                addedPrefixes.add(LINK_LOCAL_PREFIX);
            }

            if (!addedPrefixes.isEmpty()) {
                final ArrayList<RouteInfo> toBeAdded = getLocalRoutesFor(addedPrefixes);
                try {
                    // It's safe to call addInterfaceToLocalNetwork() even if
                    // the interface is already in the local_network. Note also
                    // that adding routes that already exist does not cause an
                    // error (EEXIST is silently ignored).
                    mNMService.addInterfaceToLocalNetwork(mIfName, toBeAdded);
                } catch (RemoteException e) {
                    mLog.e("Failed to add IPv6 routes to local table: " + e);
                }
            }
        }
    }

    private void configureLocalDns(
            HashSet<Inet6Address> deprecatedDnses, HashSet<Inet6Address> newDnses) {
        final INetd netd = NetdService.getInstance();
        if (netd == null) {
            if (newDnses != null) newDnses.clear();
            mLog.e("No netd service instance available; not setting local IPv6 addresses");
            return;
        }

        // [1] Remove deprecated local DNS IP addresses.
        if (!deprecatedDnses.isEmpty()) {
            for (Inet6Address dns : deprecatedDnses) {
                final String dnsString = dns.getHostAddress();
                try {
                    netd.interfaceDelAddress(mIfName, dnsString, RFC7421_PREFIX_LENGTH);
                } catch (ServiceSpecificException | RemoteException e) {
                    mLog.e("Failed to remove local dns IP " + dnsString + ": " + e);
                }
            }
        }

        // [2] Add only the local DNS IP addresses that have not previously been added.
        if (newDnses != null && !newDnses.isEmpty()) {
            final HashSet<Inet6Address> addedDnses = (HashSet) newDnses.clone();
            if (mLastRaParams != null) {
                addedDnses.removeAll(mLastRaParams.dnses);
            }

            for (Inet6Address dns : addedDnses) {
                final String dnsString = dns.getHostAddress();
                try {
                    netd.interfaceAddAddress(mIfName, dnsString, RFC7421_PREFIX_LENGTH);
                } catch (ServiceSpecificException | RemoteException e) {
                    mLog.e("Failed to add local dns IP " + dnsString + ": " + e);
                    newDnses.remove(dns);
                }
            }
        }

        try {
            netd.tetherApplyDnsInterfaces();
        } catch (ServiceSpecificException | RemoteException e) {
            mLog.e("Failed to update local DNS caching server");
            if (newDnses != null) newDnses.clear();
        }
    }

    private void setRaParams(RaParams newParams) {
        if (mRaDaemon != null) {
            final RaParams deprecatedParams =
                    RaParams.getDeprecatedRaParams(mLastRaParams, newParams);

            configureLocalRoutes(deprecatedParams.prefixes,
                    (newParams != null) ? newParams.prefixes : null);

            configureLocalDns(deprecatedParams.dnses,
                    (newParams != null) ? newParams.dnses : null);

            mRaDaemon.buildNewRa(deprecatedParams, newParams);
        }

        mLastRaParams = newParams;
    }

    // Accumulate routes representing "prefixes to be assigned to the local
    // interface", for subsequent modification of local_network routing.
    private ArrayList<RouteInfo> getLocalRoutesFor(HashSet<IpPrefix> prefixes) {
        final ArrayList<RouteInfo> localRoutes = new ArrayList<RouteInfo>();
        for (IpPrefix ipp : prefixes) {
            localRoutes.add(new RouteInfo(ipp, null, mIfName));
        }
        return localRoutes;
    }

    // Given a prefix like 2001:db8::/64 return an address like 2001:db8::1.
    private static Inet6Address getLocalDnsIpFor(IpPrefix localPrefix) {
        final byte[] dnsBytes = localPrefix.getRawAddress();
        dnsBytes[dnsBytes.length - 1] = getRandomNonZeroByte();
        try {
            return Inet6Address.getByAddress(null, dnsBytes, 0);
        } catch (UnknownHostException e) {
            Slog.wtf(TAG, "Failed to construct Inet6Address from: " + localPrefix);
            return null;
        }
    }

    private static byte getRandomNonZeroByte() {
        final byte random = (byte) (new Random()).nextInt();
        // Don't pick the subnet-router anycast address, since that might be
        // in use on the upstream already.
        return (random != 0) ? random : 0x1;
    }
}
+266 −10

File changed.

Preview size limit exceeded, changes collapsed.

+2 −4
Original line number Diff line number Diff line
@@ -66,7 +66,6 @@ public class TetherInterfaceStateMachineTest {
    @Mock private INetworkStatsService mStatsService;
    @Mock private IControlsTethering mTetherHelper;
    @Mock private InterfaceConfiguration mInterfaceConfiguration;
    @Mock private IPv6TetheringInterfaceServices mIPv6TetheringInterfaceServices;
    @Mock private SharedLog mSharedLog;

    private final TestLooper mLooper = new TestLooper();
@@ -75,7 +74,7 @@ public class TetherInterfaceStateMachineTest {
    private void initStateMachine(int interfaceType) throws Exception {
        mTestedSm = new TetherInterfaceStateMachine(
                IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog,
                mNMService, mStatsService, mTetherHelper, mIPv6TetheringInterfaceServices);
                mNMService, mStatsService, mTetherHelper);
        mTestedSm.start();
        // Starting the state machine always puts us in a consistent state and notifies
        // the test of the world that we've changed from an unknown to available state.
@@ -102,8 +101,7 @@ public class TetherInterfaceStateMachineTest {
    @Test
    public void startsOutAvailable() {
        mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(),
                TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mTetherHelper,
                mIPv6TetheringInterfaceServices);
                TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mTetherHelper);
        mTestedSm.start();
        mLooper.dispatchAll();
        verify(mTetherHelper).updateInterfaceState(