Loading packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java +12 −0 Original line number Diff line number Diff line Loading @@ -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; Loading packages/Tethering/src/android/net/ip/IpServer.java +170 −62 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -462,7 +475,7 @@ public class IpServer extends StateMachine { handleError(); } } }, new DhcpLeaseCallback()); }, new DhcpEventCallback()); } catch (RemoteException e) { throw new IllegalStateException(e); } Loading @@ -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<>(); Loading Loading @@ -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 Loading @@ -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)); Loading @@ -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 } } Loading Loading @@ -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) { Loading Loading @@ -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) { Loading @@ -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. Loading @@ -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)); } } } Loading Loading @@ -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( Loading Loading @@ -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; } Loading Loading @@ -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; } Loading packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +71 −7 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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 */, Loading Loading @@ -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); } /** Loading Loading
packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java +12 −0 Original line number Diff line number Diff line Loading @@ -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; Loading
packages/Tethering/src/android/net/ip/IpServer.java +170 −62 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -462,7 +475,7 @@ public class IpServer extends StateMachine { handleError(); } } }, new DhcpLeaseCallback()); }, new DhcpEventCallback()); } catch (RemoteException e) { throw new IllegalStateException(e); } Loading @@ -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<>(); Loading Loading @@ -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 Loading @@ -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)); Loading @@ -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 } } Loading Loading @@ -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) { Loading Loading @@ -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) { Loading @@ -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. Loading @@ -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)); } } } Loading Loading @@ -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( Loading Loading @@ -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; } Loading Loading @@ -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; } Loading
packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +71 −7 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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 */, Loading Loading @@ -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); } /** Loading