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

Commit 6feab1d3 authored by Xiao Ma's avatar Xiao Ma Committed by Automerger Merge Worker
Browse files

Merge "Support MirrorLink DHCPDECLINE." into rvc-dev am: 3ab17953

Change-Id: I4393c0ead6e0b1b7d8707a0cbf36e8c75257bcea
parents dad9a057 3ab17953
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -173,6 +173,18 @@ public class DhcpServingParamsParcelExt extends DhcpServingParamsParcel {
        return this;
    }

    /**
     * Set whether the DHCP server should request a new prefix from IpServer when receiving
     * DHCPDECLINE message in certain particular link (e.g. there is only one downstream USB
     * tethering client). If it's false, process DHCPDECLINE message as RFC2131#4.3.3 suggests.
     *
     * <p>If not set, the default value is false.
     */
    public DhcpServingParamsParcelExt setChangePrefixOnDecline(boolean changePrefixOnDecline) {
        this.changePrefixOnDecline = changePrefixOnDecline;
        return this;
    }

    private static int[] toIntArray(@NonNull Collection<Inet4Address> addrs) {
        int[] res = new int[addrs.size()];
        int i = 0;
+170 −62
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ import android.net.shared.NetdUtils;
import android.net.shared.RouteUtils;
import android.net.util.InterfaceParams;
import android.net.util.InterfaceSet;
import android.net.util.PrefixUtils;
import android.net.util.SharedLog;
import android.os.Handler;
import android.os.Looper;
@@ -60,6 +61,7 @@ import android.util.Log;
import android.util.SparseArray;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.internal.util.MessageUtils;
import com.android.internal.util.State;
@@ -115,6 +117,15 @@ public class IpServer extends StateMachine {
    private static final String ETHERNET_IFACE_ADDR = "192.168.50.1";
    private static final int ETHERNET_IFACE_PREFIX_LENGTH = 24;

    // TODO: remove this constant after introducing PrivateAddressCoordinator.
    private static final List<IpPrefix> NCM_PREFIXES = Collections.unmodifiableList(
            Arrays.asList(
                    new IpPrefix("192.168.42.0/24"),
                    new IpPrefix("192.168.51.0/24"),
                    new IpPrefix("192.168.52.0/24"),
                    new IpPrefix("192.168.53.0/24")
    ));

    // TODO: have PanService use some visible version of this constant
    private static final String BLUETOOTH_IFACE_ADDR = "192.168.44.1";
    private static final int BLUETOOTH_DHCP_PREFIX_LENGTH = 24;
@@ -212,6 +223,8 @@ public class IpServer extends StateMachine {
    public static final int CMD_IPV6_TETHER_UPDATE          = BASE_IPSERVER + 10;
    // new neighbor cache entry on our interface
    public static final int CMD_NEIGHBOR_EVENT              = BASE_IPSERVER + 11;
    // request from DHCP server that it wants to have a new prefix
    public static final int CMD_NEW_PREFIX_REQUEST          = BASE_IPSERVER + 12;

    private final State mInitialState;
    private final State mLocalHotspotState;
@@ -462,7 +475,7 @@ public class IpServer extends StateMachine {
                                handleError();
                            }
                        }
                    }, new DhcpLeaseCallback());
                    }, new DhcpEventCallback());
                } catch (RemoteException e) {
                    throw new IllegalStateException(e);
                }
@@ -475,7 +488,7 @@ public class IpServer extends StateMachine {
        }
    }

    private class DhcpLeaseCallback extends IDhcpEventCallbacks.Stub {
    private class DhcpEventCallback extends IDhcpEventCallbacks.Stub {
        @Override
        public void onLeasesChanged(List<DhcpLeaseParcelable> leaseParcelables) {
            final ArrayList<TetheredClient> leases = new ArrayList<>();
@@ -509,8 +522,9 @@ public class IpServer extends StateMachine {
        }

        @Override
        public void onNewPrefixRequest(IpPrefix currentPrefix) {
            //TODO: add specific implementation.
        public void onNewPrefixRequest(@NonNull final IpPrefix currentPrefix) {
            Objects.requireNonNull(currentPrefix);
            sendMessage(CMD_NEW_PREFIX_REQUEST, currentPrefix);
        }

        @Override
@@ -524,26 +538,38 @@ public class IpServer extends StateMachine {
        }
    }

    private RouteInfo getDirectConnectedRoute(@NonNull final LinkAddress ipv4Address) {
        Objects.requireNonNull(ipv4Address);
        return new RouteInfo(PrefixUtils.asIpPrefix(ipv4Address), null, mIfaceName, RTN_UNICAST);
    }

    private DhcpServingParamsParcel makeServingParams(@NonNull final Inet4Address defaultRouter,
            @NonNull final Inet4Address dnsServer, @NonNull LinkAddress serverAddr,
            @Nullable Inet4Address clientAddr) {
        final boolean changePrefixOnDecline =
                (mInterfaceType == TetheringManager.TETHERING_NCM && clientAddr == null);
        return new DhcpServingParamsParcelExt()
            .setDefaultRouters(defaultRouter)
            .setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS)
            .setDnsServers(dnsServer)
            .setServerAddr(serverAddr)
            .setMetered(true)
            .setSingleClientAddr(clientAddr)
            .setChangePrefixOnDecline(changePrefixOnDecline);
            // TODO: also advertise link MTU
    }

    private boolean startDhcp(final LinkAddress serverLinkAddr, final LinkAddress clientLinkAddr) {
        if (mUsingLegacyDhcp) {
            return true;
        }

        final Inet4Address addr = (Inet4Address) serverLinkAddr.getAddress();
        final int prefixLen = serverLinkAddr.getPrefixLength();
        final Inet4Address clientAddr = clientLinkAddr == null ? null :
                (Inet4Address) clientLinkAddr.getAddress();

        final DhcpServingParamsParcel params;
        params = new DhcpServingParamsParcelExt()
                .setDefaultRouters(addr)
                .setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS)
                .setDnsServers(addr)
                .setServerAddr(serverLinkAddr)
                .setMetered(true)
                .setSingleClientAddr(clientAddr);
        // TODO: also advertise link MTU

        final DhcpServingParamsParcel params = makeServingParams(addr /* defaultRouter */,
                addr /* dnsServer */, serverLinkAddr, clientAddr);
        mDhcpServerStartIndex++;
        mDeps.makeDhcpServer(
                mIfaceName, params, new DhcpServerCallbacksImpl(mDhcpServerStartIndex));
@@ -570,7 +596,7 @@ public class IpServer extends StateMachine {
                });
                mDhcpServer = null;
            } catch (RemoteException e) {
                mLog.e("Error stopping DHCP", e);
                mLog.e("Error stopping DHCP server", e);
                // Not much more we can do here
            }
        }
@@ -652,31 +678,33 @@ public class IpServer extends StateMachine {
            return false;
        }

        // Directly-connected route.
        final IpPrefix ipv4Prefix = new IpPrefix(mIpv4Address.getAddress(),
                mIpv4Address.getPrefixLength());
        final RouteInfo route = new RouteInfo(ipv4Prefix, null, null, RTN_UNICAST);
        if (enabled) {
            mLinkProperties.addLinkAddress(mIpv4Address);
            mLinkProperties.addRoute(route);
            mLinkProperties.addRoute(getDirectConnectedRoute(mIpv4Address));
        } else {
            mLinkProperties.removeLinkAddress(mIpv4Address);
            mLinkProperties.removeRoute(route);
            mLinkProperties.removeRoute(getDirectConnectedRoute(mIpv4Address));
        }

        return configureDhcp(enabled, mIpv4Address, mStaticIpv4ClientAddr);
    }

    private String getRandomWifiIPv4Address() {
    private Inet4Address getRandomIPv4Address(@NonNull final byte[] rawAddr) {
        final byte[] ipv4Addr = rawAddr;
        ipv4Addr[3] = getRandomSanitizedByte(DOUG_ADAMS, asByte(0), asByte(1), FF);
        try {
            byte[] bytes = parseNumericAddress(WIFI_HOST_IFACE_ADDR).getAddress();
            bytes[3] = getRandomSanitizedByte(DOUG_ADAMS, asByte(0), asByte(1), FF);
            return InetAddress.getByAddress(bytes).getHostAddress();
        } catch (Exception e) {
            return WIFI_HOST_IFACE_ADDR;
            return (Inet4Address) InetAddress.getByAddress(ipv4Addr);
        } catch (UnknownHostException e) {
            mLog.e("Failed to construct Inet4Address from raw IPv4 addr");
            return null;
        }
    }

    private String getRandomWifiIPv4Address() {
        final Inet4Address ipv4Addr =
                getRandomIPv4Address(parseNumericAddress(WIFI_HOST_IFACE_ADDR).getAddress());
        return ipv4Addr != null ? ipv4Addr.getHostAddress() : WIFI_HOST_IFACE_ADDR;
    }

    private boolean startIPv6() {
        mInterfaceParams = mDeps.getInterfaceParams(mIfaceName);
        if (mInterfaceParams == null) {
@@ -761,13 +789,7 @@ public class IpServer extends StateMachine {
        mLastIPv6UpstreamIfindex = upstreamIfindex;
    }

    private void configureLocalIPv6Routes(
            HashSet<IpPrefix> deprecatedPrefixes, HashSet<IpPrefix> newPrefixes) {
        // [1] Remove the routes that are deprecated.
        if (!deprecatedPrefixes.isEmpty()) {
            final ArrayList<RouteInfo> toBeRemoved =
                    getLocalRoutesFor(mIfaceName, deprecatedPrefixes);
            // Remove routes from local network.
    private void removeRoutesFromLocalNetwork(@NonNull final List<RouteInfo> toBeRemoved) {
        final int removalFailures = RouteUtils.removeRoutesFromLocalNetwork(
                mNetd, toBeRemoved);
        if (removalFailures > 0) {
@@ -778,16 +800,7 @@ public class IpServer extends StateMachine {
        for (RouteInfo route : toBeRemoved) mLinkProperties.removeRoute(route);
    }

        // [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 (!addedPrefixes.isEmpty()) {
                final ArrayList<RouteInfo> toBeAdded =
                        getLocalRoutesFor(mIfaceName, addedPrefixes);
    private void addRoutesToLocalNetwork(@NonNull final List<RouteInfo> toBeAdded) {
        try {
            // It's safe to call networkAddInterface() even if
            // the interface is already in the local_network.
@@ -797,14 +810,34 @@ public class IpServer extends StateMachine {
                // already exist does not cause an error (EEXIST is silently ignored).
                RouteUtils.addRoutesToLocalNetwork(mNetd, mIfaceName, toBeAdded);
            } catch (IllegalStateException e) {
                        mLog.e("Failed to add IPv6 routes to local table: " + e);
                mLog.e("Failed to add IPv4/v6 routes to local table: " + e);
                return;
            }
        } catch (ServiceSpecificException | RemoteException e) {
            mLog.e("Failed to add " + mIfaceName + " to local table: ", e);
            return;
        }

        for (RouteInfo route : toBeAdded) mLinkProperties.addRoute(route);
    }

    private void configureLocalIPv6Routes(
            HashSet<IpPrefix> deprecatedPrefixes, HashSet<IpPrefix> newPrefixes) {
        // [1] Remove the routes that are deprecated.
        if (!deprecatedPrefixes.isEmpty()) {
            removeRoutesFromLocalNetwork(getLocalRoutesFor(mIfaceName, deprecatedPrefixes));
        }

        // [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 (!addedPrefixes.isEmpty()) {
                addRoutesToLocalNetwork(getLocalRoutesFor(mIfaceName, addedPrefixes));
            }
        }
    }

@@ -945,6 +978,80 @@ public class IpServer extends StateMachine {
        }
    }

    // TODO: call PrivateAddressCoordinator.requestDownstreamAddress instead of this temporary
    // logic.
    private Inet4Address requestDownstreamAddress(@NonNull final IpPrefix currentPrefix) {
        final int oldIndex = NCM_PREFIXES.indexOf(currentPrefix);
        if (oldIndex == -1) {
            mLog.e("current prefix isn't supported for NCM link: " + currentPrefix);
            return null;
        }

        final IpPrefix newPrefix = NCM_PREFIXES.get((oldIndex + 1) % NCM_PREFIXES.size());
        return getRandomIPv4Address(newPrefix.getRawAddress());
    }

    private void handleNewPrefixRequest(@NonNull final IpPrefix currentPrefix) {
        if (!currentPrefix.contains(mIpv4Address.getAddress())
                || currentPrefix.getPrefixLength() != mIpv4Address.getPrefixLength()) {
            Log.e(TAG, "Invalid prefix: " + currentPrefix);
            return;
        }

        final LinkAddress deprecatedLinkAddress = mIpv4Address;
        final Inet4Address srvAddr = requestDownstreamAddress(currentPrefix);
        if (srvAddr == null) {
            mLog.e("Fail to request a new downstream prefix");
            return;
        }
        mIpv4Address = new LinkAddress(srvAddr, currentPrefix.getPrefixLength());

        // Add new IPv4 address on the interface.
        if (!mInterfaceCtrl.addAddress(srvAddr, currentPrefix.getPrefixLength())) {
            mLog.e("Failed to add new IP " + srvAddr);
            return;
        }

        // Remove deprecated routes from local network.
        removeRoutesFromLocalNetwork(
                Collections.singletonList(getDirectConnectedRoute(deprecatedLinkAddress)));
        mLinkProperties.removeLinkAddress(deprecatedLinkAddress);

        // Add new routes to local network.
        addRoutesToLocalNetwork(
                Collections.singletonList(getDirectConnectedRoute(mIpv4Address)));
        mLinkProperties.addLinkAddress(mIpv4Address);

        // Update local DNS caching server with new IPv4 address, otherwise, dnsmasq doesn't
        // listen on the interface configured with new IPv4 address, that results DNS validation
        // failure of downstream client even if appropriate routes have been configured.
        try {
            mNetd.tetherApplyDnsInterfaces();
        } catch (ServiceSpecificException | RemoteException e) {
            mLog.e("Failed to update local DNS caching server");
            return;
        }
        sendLinkProperties();

        // Notify DHCP server that new prefix/route has been applied on IpServer.
        final Inet4Address clientAddr = mStaticIpv4ClientAddr == null ? null :
                (Inet4Address) mStaticIpv4ClientAddr.getAddress();
        final DhcpServingParamsParcel params = makeServingParams(srvAddr /* defaultRouter */,
                srvAddr /* dnsServer */, mIpv4Address /* serverLinkAddress */, clientAddr);
        try {
            mDhcpServer.updateParams(params, new OnHandlerStatusCallback() {
                    @Override
                    public void callback(int statusCode) {
                        if (statusCode != STATUS_SUCCESS) {
                            mLog.e("Error updating DHCP serving params: " + statusCode);
                        }
                    }
            });
        } catch (RemoteException e) {
            mLog.e("Error updating DHCP serving params", e);
        }
    }

    private byte getHopLimit(String upstreamIface) {
        try {
            int upstreamHopLimit = Integer.parseUnsignedInt(
@@ -1056,11 +1163,9 @@ public class IpServer extends StateMachine {
            }

            try {
                final IpPrefix ipv4Prefix = new IpPrefix(mIpv4Address.getAddress(),
                        mIpv4Address.getPrefixLength());
                NetdUtils.tetherInterface(mNetd, mIfaceName, ipv4Prefix);
                NetdUtils.tetherInterface(mNetd, mIfaceName, PrefixUtils.asIpPrefix(mIpv4Address));
            } catch (RemoteException | ServiceSpecificException | IllegalStateException e) {
                mLog.e("Error Tethering: " + e);
                mLog.e("Error Tethering", e);
                mLastError = TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
                return;
            }
@@ -1115,6 +1220,9 @@ public class IpServer extends StateMachine {
                    mLastError = TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
                    transitionTo(mInitialState);
                    break;
                case CMD_NEW_PREFIX_REQUEST:
                    handleNewPrefixRequest((IpPrefix) message.obj);
                    break;
                default:
                    return false;
            }
+71 −7
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.net.ip;

import static android.net.INetd.IF_STATE_UP;
import static android.net.TetheringManager.TETHERING_BLUETOOTH;
import static android.net.TetheringManager.TETHERING_NCM;
import static android.net.TetheringManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
@@ -38,6 +39,7 @@ import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -67,6 +69,7 @@ import android.net.MacAddress;
import android.net.RouteInfo;
import android.net.TetherOffloadRuleParcel;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.IDhcpEventCallbacks;
import android.net.dhcp.IDhcpServer;
import android.net.dhcp.IDhcpServerCallbacks;
import android.net.ip.IpNeighborMonitor.NeighborEvent;
@@ -94,6 +97,7 @@ import org.mockito.MockitoAnnotations;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.List;

@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -496,6 +500,59 @@ public class IpServerTest {
        assertDhcpStarted(new IpPrefix("192.168.49.0/24"));
    }

    @Test
    public void startsDhcpServerOnNcm() throws Exception {
        initStateMachine(TETHERING_NCM);
        dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY);
        dispatchTetherConnectionChanged(UPSTREAM_IFACE);

        assertDhcpStarted(new IpPrefix("192.168.42.0/24"));
    }

    @Test
    public void testOnNewPrefixRequest() throws Exception {
        initStateMachine(TETHERING_NCM);
        dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY);

        final IDhcpEventCallbacks eventCallbacks;
        final ArgumentCaptor<IDhcpEventCallbacks> dhcpEventCbsCaptor =
                 ArgumentCaptor.forClass(IDhcpEventCallbacks.class);
        verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).startWithCallbacks(
                any(), dhcpEventCbsCaptor.capture());
        eventCallbacks = dhcpEventCbsCaptor.getValue();
        assertDhcpStarted(new IpPrefix("192.168.42.0/24"));

        // Simulate the DHCP server receives DHCPDECLINE on MirrorLink and then signals
        // onNewPrefixRequest callback.
        eventCallbacks.onNewPrefixRequest(new IpPrefix("192.168.42.0/24"));
        mLooper.dispatchAll();

        final ArgumentCaptor<LinkProperties> lpCaptor =
                ArgumentCaptor.forClass(LinkProperties.class);
        InOrder inOrder = inOrder(mNetd, mCallback);
        inOrder.verify(mCallback).updateInterfaceState(
                mIpServer, STATE_LOCAL_ONLY, TETHER_ERROR_NO_ERROR);
        inOrder.verify(mCallback).updateLinkProperties(eq(mIpServer), lpCaptor.capture());
        inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
        // One for ipv4 route, one for ipv6 link local route.
        inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
                any(), any());
        inOrder.verify(mNetd).tetherApplyDnsInterfaces();
        inOrder.verify(mCallback).updateLinkProperties(eq(mIpServer), lpCaptor.capture());
        verifyNoMoreInteractions(mCallback);

        final LinkProperties linkProperties = lpCaptor.getValue();
        final List<LinkAddress> linkAddresses = linkProperties.getLinkAddresses();
        assertEquals(1, linkProperties.getLinkAddresses().size());
        assertEquals(1, linkProperties.getRoutes().size());
        final IpPrefix prefix = new IpPrefix(linkAddresses.get(0).getAddress(),
                linkAddresses.get(0).getPrefixLength());
        assertNotEquals(prefix, new IpPrefix("192.168.42.0/24"));

        verify(mDhcpServer).updateParams(mDhcpParamsCaptor.capture(), any());
        assertDhcpServingParams(mDhcpParamsCaptor.getValue(), prefix);
    }

    @Test
    public void doesNotStartDhcpServerIfDisabled() throws Exception {
        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, true /* usingLegacyDhcp */,
@@ -731,19 +788,26 @@ public class IpServerTest {
        verify(mIpNeighborMonitor, never()).start();
    }

    private void assertDhcpStarted(IpPrefix expectedPrefix) throws Exception {
        verify(mDependencies, times(1)).makeDhcpServer(eq(IFACE_NAME), any(), any());
        verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).startWithCallbacks(
                any(), any());
        final DhcpServingParamsParcel params = mDhcpParamsCaptor.getValue();
    private void assertDhcpServingParams(final DhcpServingParamsParcel params,
            final IpPrefix prefix) {
        // Last address byte is random
        assertTrue(expectedPrefix.contains(intToInet4AddressHTH(params.serverAddr)));
        assertEquals(expectedPrefix.getPrefixLength(), params.serverAddrPrefixLength);
        assertTrue(prefix.contains(intToInet4AddressHTH(params.serverAddr)));
        assertEquals(prefix.getPrefixLength(), params.serverAddrPrefixLength);
        assertEquals(1, params.defaultRouters.length);
        assertEquals(params.serverAddr, params.defaultRouters[0]);
        assertEquals(1, params.dnsServers.length);
        assertEquals(params.serverAddr, params.dnsServers[0]);
        assertEquals(DHCP_LEASE_TIME_SECS, params.dhcpLeaseTimeSecs);
        if (mIpServer.interfaceType() == TETHERING_NCM) {
            assertTrue(params.changePrefixOnDecline);
        }
    }

    private void assertDhcpStarted(IpPrefix expectedPrefix) throws Exception {
        verify(mDependencies, times(1)).makeDhcpServer(eq(IFACE_NAME), any(), any());
        verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).startWithCallbacks(
                any(), any());
        assertDhcpServingParams(mDhcpParamsCaptor.getValue(), expectedPrefix);
    }

    /**