diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt index ee997f1542992..c3c31f5b81c0a 100644 --- a/api/module-lib-current.txt +++ b/api/module-lib-current.txt @@ -127,9 +127,11 @@ package android.net { public static class TetheringManager.TetheringRequest.Builder { ctor public TetheringManager.TetheringRequest.Builder(int); method @NonNull public android.net.TetheringManager.TetheringRequest build(); + method @Nullable public android.net.LinkAddress getClientStaticIpv4Address(); + method @Nullable public android.net.LinkAddress getLocalIpv4Address(); method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean); method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setSilentProvisioning(boolean); - method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder useStaticIpv4Addresses(@NonNull android.net.LinkAddress); + method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress); } } diff --git a/api/system-current.txt b/api/system-current.txt index fd3798908d1ea..ae48477d60e68 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -6597,9 +6597,11 @@ package android.net { public static class TetheringManager.TetheringRequest.Builder { ctor public TetheringManager.TetheringRequest.Builder(int); method @NonNull public android.net.TetheringManager.TetheringRequest build(); + method @Nullable public android.net.LinkAddress getClientStaticIpv4Address(); + method @Nullable public android.net.LinkAddress getLocalIpv4Address(); method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean); method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setSilentProvisioning(boolean); - method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder useStaticIpv4Addresses(@NonNull android.net.LinkAddress); + method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress); } public class TrafficStats { diff --git a/api/test-current.txt b/api/test-current.txt index 66d6af9b67c78..c83eed823828f 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1975,9 +1975,11 @@ package android.net { public static class TetheringManager.TetheringRequest.Builder { ctor public TetheringManager.TetheringRequest.Builder(int); method @NonNull public android.net.TetheringManager.TetheringRequest build(); + method @Nullable public android.net.LinkAddress getClientStaticIpv4Address(); + method @Nullable public android.net.LinkAddress getLocalIpv4Address(); method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean); method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setSilentProvisioning(boolean); - method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder useStaticIpv4Addresses(@NonNull android.net.LinkAddress); + method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress); } public class TrafficStats { diff --git a/packages/Tethering/common/TetheringLib/api/module-lib-current.txt b/packages/Tethering/common/TetheringLib/api/module-lib-current.txt index e25d77dfc9a81..8a7e975051a58 100644 --- a/packages/Tethering/common/TetheringLib/api/module-lib-current.txt +++ b/packages/Tethering/common/TetheringLib/api/module-lib-current.txt @@ -117,9 +117,11 @@ package android.net { public static class TetheringManager.TetheringRequest.Builder { ctor public TetheringManager.TetheringRequest.Builder(int); method @NonNull public android.net.TetheringManager.TetheringRequest build(); + method @Nullable public android.net.LinkAddress getClientStaticIpv4Address(); + method @Nullable public android.net.LinkAddress getLocalIpv4Address(); method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean); method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setSilentProvisioning(boolean); - method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder useStaticIpv4Addresses(@NonNull android.net.LinkAddress); + method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress); } } diff --git a/packages/Tethering/common/TetheringLib/api/system-current.txt b/packages/Tethering/common/TetheringLib/api/system-current.txt index d6fcb625bdae9..ac739532a4909 100644 --- a/packages/Tethering/common/TetheringLib/api/system-current.txt +++ b/packages/Tethering/common/TetheringLib/api/system-current.txt @@ -95,9 +95,11 @@ package android.net { public static class TetheringManager.TetheringRequest.Builder { ctor public TetheringManager.TetheringRequest.Builder(int); method @NonNull public android.net.TetheringManager.TetheringRequest build(); + method @Nullable public android.net.LinkAddress getClientStaticIpv4Address(); + method @Nullable public android.net.LinkAddress getLocalIpv4Address(); method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean); method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setSilentProvisioning(boolean); - method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder useStaticIpv4Addresses(@NonNull android.net.LinkAddress); + method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress); } } diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java index 7f831ced7beca..f2045df947e4b 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java @@ -512,19 +512,36 @@ public class TetheringManager { mBuilderParcel = new TetheringRequestParcel(); mBuilderParcel.tetheringType = type; mBuilderParcel.localIPv4Address = null; + mBuilderParcel.staticClientAddress = null; mBuilderParcel.exemptFromEntitlementCheck = false; mBuilderParcel.showProvisioningUi = true; } /** - * Configure tethering with static IPv4 assignment (with DHCP disabled). + * Configure tethering with static IPv4 assignment. * - * @param localIPv4Address The preferred local IPv4 address to use. + * The clientAddress must be in the localIPv4Address prefix. A DHCP server will be + * started, but will only be able to offer the client address. The two addresses must + * be in the same prefix. + * + * @param localIPv4Address The preferred local IPv4 link address to use. + * @param clientAddress The static client address. */ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) @NonNull - public Builder useStaticIpv4Addresses(@NonNull final LinkAddress localIPv4Address) { + public Builder setStaticIpv4Addresses(@NonNull final LinkAddress localIPv4Address, + @NonNull final LinkAddress clientAddress) { + Objects.requireNonNull(localIPv4Address); + Objects.requireNonNull(clientAddress); + if (localIPv4Address.getPrefixLength() != clientAddress.getPrefixLength() + || !localIPv4Address.isIpv4() || !clientAddress.isIpv4() + || !new IpPrefix(localIPv4Address.toString()).equals( + new IpPrefix(clientAddress.toString()))) { + throw new IllegalArgumentException("Invalid server or client addresses"); + } + mBuilderParcel.localIPv4Address = localIPv4Address; + mBuilderParcel.staticClientAddress = clientAddress; return this; } @@ -549,6 +566,18 @@ public class TetheringManager { public TetheringRequest build() { return new TetheringRequest(mBuilderParcel); } + + /** Get static server address. */ + @Nullable + public LinkAddress getLocalIpv4Address() { + return mBuilderParcel.localIPv4Address; + } + + /** Get static client address. */ + @Nullable + public LinkAddress getClientStaticIpv4Address() { + return mBuilderParcel.staticClientAddress; + } } /** @@ -563,6 +592,7 @@ public class TetheringManager { public String toString() { return "TetheringRequest [ type= " + mRequestParcel.tetheringType + ", localIPv4Address= " + mRequestParcel.localIPv4Address + + ", staticClientAddress= " + mRequestParcel.staticClientAddress + ", exemptFromEntitlementCheck= " + mRequestParcel.exemptFromEntitlementCheck + ", showProvisioningUi= " + mRequestParcel.showProvisioningUi + " ]"; diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl b/packages/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl index bf19d85f6a836..c0280d3dbfaf1 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl @@ -25,6 +25,7 @@ import android.net.LinkAddress; parcelable TetheringRequestParcel { int tetheringType; LinkAddress localIPv4Address; + LinkAddress staticClientAddress; boolean exemptFromEntitlementCheck; boolean showProvisioningUi; } diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java index 6c0c432d46dcd..433b903ebe521 100644 --- a/packages/Tethering/src/android/net/ip/IpServer.java +++ b/packages/Tethering/src/android/net/ip/IpServer.java @@ -35,6 +35,7 @@ import android.net.MacAddress; import android.net.RouteInfo; import android.net.TetheredClient; import android.net.TetheringManager; +import android.net.TetheringRequestParcel; import android.net.dhcp.DhcpLeaseParcelable; import android.net.dhcp.DhcpServerCallbacks; import android.net.dhcp.DhcpServingParamsParcel; @@ -243,6 +244,10 @@ public class IpServer extends StateMachine { private IDhcpServer mDhcpServer; private RaParams mLastRaParams; private LinkAddress mIpv4Address; + + private LinkAddress mStaticIpv4ServerAddr; + private LinkAddress mStaticIpv4ClientAddr; + @NonNull private List mDhcpLeases = Collections.emptyList(); @@ -547,6 +552,8 @@ public class IpServer extends StateMachine { // into calls to InterfaceController, shared with startIPv4(). mInterfaceCtrl.clearIPv4Address(); mIpv4Address = null; + mStaticIpv4ServerAddr = null; + mStaticIpv4ClientAddr = null; } private boolean configureIPv4(boolean enabled) { @@ -557,7 +564,10 @@ public class IpServer extends StateMachine { final Inet4Address srvAddr; int prefixLen = 0; try { - if (mInterfaceType == TetheringManager.TETHERING_USB + if (mStaticIpv4ServerAddr != null) { + srvAddr = (Inet4Address) mStaticIpv4ServerAddr.getAddress(); + prefixLen = mStaticIpv4ServerAddr.getPrefixLength(); + } else if (mInterfaceType == TetheringManager.TETHERING_USB || mInterfaceType == TetheringManager.TETHERING_NCM) { srvAddr = (Inet4Address) parseNumericAddress(USB_NEAR_IFACE_ADDR); prefixLen = USB_PREFIX_LENGTH; @@ -602,10 +612,6 @@ public class IpServer extends StateMachine { return false; } - if (!configureDhcp(enabled, srvAddr, prefixLen)) { - return false; - } - // Directly-connected route. final IpPrefix ipv4Prefix = new IpPrefix(mIpv4Address.getAddress(), mIpv4Address.getPrefixLength()); @@ -617,7 +623,8 @@ public class IpServer extends StateMachine { mLinkProperties.removeLinkAddress(mIpv4Address); mLinkProperties.removeRoute(route); } - return true; + + return configureDhcp(enabled, srvAddr, prefixLen); } private String getRandomWifiIPv4Address() { @@ -937,6 +944,13 @@ public class IpServer extends StateMachine { mLinkProperties.setInterfaceName(mIfaceName); } + private void maybeConfigureStaticIp(final TetheringRequestParcel request) { + if (request == null) return; + + mStaticIpv4ServerAddr = request.localIPv4Address; + mStaticIpv4ClientAddr = request.staticClientAddress; + } + class InitialState extends State { @Override public void enter() { @@ -951,9 +965,11 @@ public class IpServer extends StateMachine { mLastError = TetheringManager.TETHER_ERROR_NO_ERROR; switch (message.arg1) { case STATE_LOCAL_ONLY: + maybeConfigureStaticIp((TetheringRequestParcel) message.obj); transitionTo(mLocalHotspotState); break; case STATE_TETHERED: + maybeConfigureStaticIp((TetheringRequestParcel) message.obj); transitionTo(mTetheredState); break; default: diff --git a/packages/Tethering/src/android/net/util/TetheringUtils.java b/packages/Tethering/src/android/net/util/TetheringUtils.java index 5a6d5c1cbfb00..dd67dddae1cd7 100644 --- a/packages/Tethering/src/android/net/util/TetheringUtils.java +++ b/packages/Tethering/src/android/net/util/TetheringUtils.java @@ -15,8 +15,11 @@ */ package android.net.util; +import android.net.TetheringRequestParcel; + import java.io.FileDescriptor; import java.net.SocketException; +import java.util.Objects; /** * Native methods for tethering utilization. @@ -38,4 +41,17 @@ public class TetheringUtils { public static int uint16(short s) { return s & 0xffff; } + + /** Check whether two TetheringRequestParcels are the same. */ + public static boolean isTetheringRequestEquals(final TetheringRequestParcel request, + final TetheringRequestParcel otherRequest) { + if (request == otherRequest) return true; + + return request != null && otherRequest != null + && request.tetheringType == otherRequest.tetheringType + && Objects.equals(request.localIPv4Address, otherRequest.localIPv4Address) + && Objects.equals(request.staticClientAddress, otherRequest.staticClientAddress) + && request.exemptFromEntitlementCheck == otherRequest.exemptFromEntitlementCheck + && request.showProvisioningUi == otherRequest.showProvisioningUi; + } } diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java index 3d8dbab7d4fb6..6e918929f87ea 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java @@ -92,6 +92,7 @@ import android.net.util.BaseNetdUnsolicitedEventListener; import android.net.util.InterfaceSet; import android.net.util.PrefixUtils; import android.net.util.SharedLog; +import android.net.util.TetheringUtils; import android.net.util.VersionedBroadcastListener; import android.net.wifi.WifiClient; import android.net.wifi.WifiManager; @@ -196,6 +197,11 @@ public class Tethering { private final SharedLog mLog = new SharedLog(TAG); private final RemoteCallbackList mTetheringEventCallbacks = new RemoteCallbackList<>(); + // Currently active tethering requests per tethering type. Only one of each type can be + // requested at a time. After a tethering type is requested, the map keeps tethering parameters + // to be used after the interface comes up asynchronously. + private final SparseArray mActiveTetheringRequests = + new SparseArray<>(); // used to synchronize public access to members private final Object mPublicSync; @@ -487,14 +493,31 @@ public class Tethering { } void startTethering(final TetheringRequestParcel request, final IIntResultListener listener) { - mEntitlementMgr.startProvisioningIfNeeded(request.tetheringType, - request.showProvisioningUi); - enableTetheringInternal(request.tetheringType, true /* enabled */, listener); + mHandler.post(() -> { + final TetheringRequestParcel unfinishedRequest = mActiveTetheringRequests.get( + request.tetheringType); + // If tethering is already enabled with a different request, + // disable before re-enabling. + if (unfinishedRequest != null + && !TetheringUtils.isTetheringRequestEquals(unfinishedRequest, request)) { + enableTetheringInternal(request.tetheringType, false /* disabled */, null); + mEntitlementMgr.stopProvisioningIfNeeded(request.tetheringType); + } + mActiveTetheringRequests.put(request.tetheringType, request); + + mEntitlementMgr.startProvisioningIfNeeded(request.tetheringType, + request.showProvisioningUi); + enableTetheringInternal(request.tetheringType, true /* enabled */, listener); + }); } void stopTethering(int type) { - enableTetheringInternal(type, false /* disabled */, null); - mEntitlementMgr.stopProvisioningIfNeeded(type); + mHandler.post(() -> { + mActiveTetheringRequests.remove(type); + + enableTetheringInternal(type, false /* disabled */, null); + mEntitlementMgr.stopProvisioningIfNeeded(type); + }); } /** @@ -503,39 +526,45 @@ public class Tethering { */ private void enableTetheringInternal(int type, boolean enable, final IIntResultListener listener) { - int result; + int result = TETHER_ERROR_NO_ERROR; switch (type) { case TETHERING_WIFI: result = setWifiTethering(enable); - sendTetherResult(listener, result); break; case TETHERING_USB: result = setUsbTethering(enable); - sendTetherResult(listener, result); break; case TETHERING_BLUETOOTH: setBluetoothTethering(enable, listener); break; case TETHERING_NCM: result = setNcmTethering(enable); - sendTetherResult(listener, result); break; case TETHERING_ETHERNET: result = setEthernetTethering(enable); - sendTetherResult(listener, result); break; default: Log.w(TAG, "Invalid tether type."); - sendTetherResult(listener, TETHER_ERROR_UNKNOWN_IFACE); + result = TETHER_ERROR_UNKNOWN_IFACE; + } + + // The result of Bluetooth tethering will be sent by #setBluetoothTethering. + if (type != TETHERING_BLUETOOTH) { + sendTetherResult(listener, result, type); } } - private void sendTetherResult(final IIntResultListener listener, int result) { + private void sendTetherResult(final IIntResultListener listener, final int result, + final int type) { if (listener != null) { try { listener.onResult(result); } catch (RemoteException e) { } } + + // If changing tethering fail, remove corresponding request + // no matter who trigger the start/stop. + if (result != TETHER_ERROR_NO_ERROR) mActiveTetheringRequests.remove(type); } private int setWifiTethering(final boolean enable) { @@ -565,7 +594,7 @@ public class Tethering { if (adapter == null || !adapter.isEnabled()) { Log.w(TAG, "Tried to enable bluetooth tethering with null or disabled adapter. null: " + (adapter == null)); - sendTetherResult(listener, TETHER_ERROR_SERVICE_UNAVAIL); + sendTetherResult(listener, TETHER_ERROR_SERVICE_UNAVAIL, TETHERING_BLUETOOTH); return; } @@ -594,7 +623,7 @@ public class Tethering { final int result = (((BluetoothPan) proxy).isTetheringOn() == enable) ? TETHER_ERROR_NO_ERROR : TETHER_ERROR_MASTER_ERROR; - sendTetherResult(listener, result); + sendTetherResult(listener, result, TETHERING_BLUETOOTH); adapter.closeProfileProxy(BluetoothProfile.PAN, proxy); } }, BluetoothProfile.PAN); @@ -672,12 +701,18 @@ public class Tethering { Log.e(TAG, "Tried to Tether an unavailable iface: " + iface + ", ignoring"); return TETHER_ERROR_UNAVAIL_IFACE; } - // NOTE: If a CMD_TETHER_REQUESTED message is already in the TISM's - // queue but not yet processed, this will be a no-op and it will not - // return an error. + // NOTE: If a CMD_TETHER_REQUESTED message is already in the TISM's queue but not yet + // processed, this will be a no-op and it will not return an error. // - // TODO: reexamine the threading and messaging model. - tetherState.ipServer.sendMessage(IpServer.CMD_TETHER_REQUESTED, requestedState); + // This code cannot race with untether() because they both synchronize on mPublicSync. + // TODO: reexamine the threading and messaging model to totally remove mPublicSync. + final int type = tetherState.ipServer.interfaceType(); + final TetheringRequestParcel request = mActiveTetheringRequests.get(type, null); + if (request != null) { + mActiveTetheringRequests.delete(type); + } + tetherState.ipServer.sendMessage(IpServer.CMD_TETHER_REQUESTED, requestedState, 0, + request); return TETHER_ERROR_NO_ERROR; } } diff --git a/packages/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java b/packages/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java new file mode 100644 index 0000000000000..1499f3be224e9 --- /dev/null +++ b/packages/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2019 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 android.net.util; + +import static android.net.TetheringManager.TETHERING_USB; +import static android.net.TetheringManager.TETHERING_WIFI; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import android.net.LinkAddress; +import android.net.TetheringRequestParcel; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.testutils.MiscAssertsKt; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class TetheringUtilsTest { + private static final LinkAddress TEST_SERVER_ADDR = new LinkAddress("192.168.43.1/24"); + private static final LinkAddress TEST_CLIENT_ADDR = new LinkAddress("192.168.43.5/24"); + private TetheringRequestParcel mTetheringRequest; + + @Before + public void setUp() { + mTetheringRequest = makeTetheringRequestParcel(); + } + + public TetheringRequestParcel makeTetheringRequestParcel() { + final TetheringRequestParcel request = new TetheringRequestParcel(); + request.tetheringType = TETHERING_WIFI; + request.localIPv4Address = TEST_SERVER_ADDR; + request.staticClientAddress = TEST_CLIENT_ADDR; + request.exemptFromEntitlementCheck = false; + request.showProvisioningUi = true; + return request; + } + + @Test + public void testIsTetheringRequestEquals() throws Exception { + TetheringRequestParcel request = makeTetheringRequestParcel(); + + assertTrue(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, mTetheringRequest)); + assertTrue(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request)); + assertTrue(TetheringUtils.isTetheringRequestEquals(null, null)); + assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, null)); + assertFalse(TetheringUtils.isTetheringRequestEquals(null, mTetheringRequest)); + + request = makeTetheringRequestParcel(); + request.tetheringType = TETHERING_USB; + assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request)); + + request = makeTetheringRequestParcel(); + request.localIPv4Address = null; + request.staticClientAddress = null; + assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request)); + + request = makeTetheringRequestParcel(); + request.exemptFromEntitlementCheck = true; + assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request)); + + request = makeTetheringRequestParcel(); + request.showProvisioningUi = false; + assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request)); + + MiscAssertsKt.assertFieldCountEquals(5, TetheringRequestParcel.class); + } +} diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java index 820c852a271a8..60d7ad1c5b1e1 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java @@ -82,6 +82,7 @@ import android.hardware.usb.UsbManager; import android.net.ConnectivityManager; import android.net.EthernetManager; import android.net.EthernetManager.TetheredInterfaceRequest; +import android.net.IIntResultListener; import android.net.INetd; import android.net.ITetheringEventCallback; import android.net.InetAddresses; @@ -499,10 +500,16 @@ public class TetheringTest { return new Tethering(mTetheringDependencies); } - private TetheringRequestParcel createTetheringRquestParcel(final int type) { + private TetheringRequestParcel createTetheringRequestParcel(final int type) { + return createTetheringRequestParcel(type, null, null); + } + + private TetheringRequestParcel createTetheringRequestParcel(final int type, + final LinkAddress serverAddr, final LinkAddress clientAddr) { final TetheringRequestParcel request = new TetheringRequestParcel(); request.tetheringType = type; - request.localIPv4Address = null; + request.localIPv4Address = serverAddr; + request.staticClientAddress = clientAddr; request.exemptFromEntitlementCheck = false; request.showProvisioningUi = false; @@ -616,7 +623,7 @@ public class TetheringTest { private void prepareNcmTethering() { // Emulate startTethering(TETHERING_NCM) called - mTethering.startTethering(createTetheringRquestParcel(TETHERING_NCM), null); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), null); mLooper.dispatchAll(); verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NCM); @@ -629,7 +636,7 @@ public class TetheringTest { .thenReturn(upstreamState); // Emulate pressing the USB tethering button in Settings UI. - mTethering.startTethering(createTetheringRquestParcel(TETHERING_USB), null); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), null); mLooper.dispatchAll(); verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS); @@ -903,7 +910,7 @@ public class TetheringTest { when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true); // Emulate pressing the WiFi tethering button. - mTethering.startTethering(createTetheringRquestParcel(TETHERING_WIFI), null); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null); mLooper.dispatchAll(); verify(mWifiManager, times(1)).startTetheredHotspot(null); verifyNoMoreInteractions(mWifiManager); @@ -931,7 +938,7 @@ public class TetheringTest { when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true); // Emulate pressing the WiFi tethering button. - mTethering.startTethering(createTetheringRquestParcel(TETHERING_WIFI), null); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null); mLooper.dispatchAll(); verify(mWifiManager, times(1)).startTetheredHotspot(null); verifyNoMoreInteractions(mWifiManager); @@ -1008,7 +1015,7 @@ public class TetheringTest { doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME); // Emulate pressing the WiFi tethering button. - mTethering.startTethering(createTetheringRquestParcel(TETHERING_WIFI), null); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null); mLooper.dispatchAll(); verify(mWifiManager, times(1)).startTetheredHotspot(null); verifyNoMoreInteractions(mWifiManager); @@ -1303,7 +1310,7 @@ public class TetheringTest { tetherState = callback.pollTetherStatesChanged(); assertArrayEquals(tetherState.availableList, new String[] {TEST_WLAN_IFNAME}); - mTethering.startTethering(createTetheringRquestParcel(TETHERING_WIFI), null); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null); sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); mLooper.dispatchAll(); tetherState = callback.pollTetherStatesChanged(); @@ -1398,10 +1405,10 @@ public class TetheringTest { public void testNoDuplicatedEthernetRequest() throws Exception { final TetheredInterfaceRequest mockRequest = mock(TetheredInterfaceRequest.class); when(mEm.requestTetheredInterface(any(), any())).thenReturn(mockRequest); - mTethering.startTethering(createTetheringRquestParcel(TETHERING_ETHERNET), null); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), null); mLooper.dispatchAll(); verify(mEm, times(1)).requestTetheredInterface(any(), any()); - mTethering.startTethering(createTetheringRquestParcel(TETHERING_ETHERNET), null); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), null); mLooper.dispatchAll(); verifyNoMoreInteractions(mEm); mTethering.stopTethering(TETHERING_ETHERNET); @@ -1580,6 +1587,86 @@ public class TetheringTest { assertTrue(element + " not found in " + collection, collection.contains(element)); } + private class ResultListener extends IIntResultListener.Stub { + private final int mExpectedResult; + private boolean mHasResult = false; + ResultListener(final int expectedResult) { + mExpectedResult = expectedResult; + } + + @Override + public void onResult(final int resultCode) { + mHasResult = true; + if (resultCode != mExpectedResult) { + fail("expected result: " + mExpectedResult + " but actual result: " + resultCode); + } + } + + public void assertHasResult() { + if (!mHasResult) fail("No callback result"); + } + } + + @Test + public void testMultipleStartTethering() throws Exception { + final LinkAddress serverLinkAddr = new LinkAddress("192.168.20.1/24"); + final LinkAddress clientLinkAddr = new LinkAddress("192.168.20.42/24"); + final String serverAddr = "192.168.20.1"; + final ResultListener firstResult = new ResultListener(TETHER_ERROR_NO_ERROR); + final ResultListener secondResult = new ResultListener(TETHER_ERROR_NO_ERROR); + final ResultListener thirdResult = new ResultListener(TETHER_ERROR_NO_ERROR); + + // Enable USB tethering and check that Tethering starts USB. + mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB, + null, null), firstResult); + mLooper.dispatchAll(); + firstResult.assertHasResult(); + verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS); + verifyNoMoreInteractions(mUsbManager); + + // Enable USB tethering again with the same request and expect no change to USB. + mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB, + null, null), secondResult); + mLooper.dispatchAll(); + secondResult.assertHasResult(); + verify(mUsbManager, never()).setCurrentFunctions(UsbManager.FUNCTION_NONE); + reset(mUsbManager); + + // Enable USB tethering with a different request and expect that USB is stopped and + // started. + mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB, + serverLinkAddr, clientLinkAddr), thirdResult); + mLooper.dispatchAll(); + thirdResult.assertHasResult(); + verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NONE); + verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS); + + // Expect that when USB comes up, the DHCP server is configured with the requested address. + mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true); + sendUsbBroadcast(true, true, true, TETHERING_USB); + mLooper.dispatchAll(); + verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks( + any(), any()); + verify(mNetd).interfaceSetCfg(argThat(cfg -> serverAddr.equals(cfg.ipv4Addr))); + } + + @Test + public void testRequestStaticServerIp() throws Exception { + final LinkAddress serverLinkAddr = new LinkAddress("192.168.20.1/24"); + final LinkAddress clientLinkAddr = new LinkAddress("192.168.20.42/24"); + final String serverAddr = "192.168.20.1"; + mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB, + serverLinkAddr, clientLinkAddr), null); + mLooper.dispatchAll(); + verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS); + mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true); + sendUsbBroadcast(true, true, true, TETHERING_USB); + mLooper.dispatchAll(); + verify(mNetd).interfaceSetCfg(argThat(cfg -> serverAddr.equals(cfg.ipv4Addr))); + + // TODO: test static client address. + } + // TODO: Test that a request for hotspot mode doesn't interfere with an // already operating tethering mode interface. }