Support static address configuration
Application can specify static ipv4 server and client address to setup
tethering and this is one shot configuration. Tethering service would
not save the configuration and the configuration would be reset when
tethering stop or start failure.
When startTethering callback fired, it just mean tethering is requested
successful. Therefore, callers may call startTethering again if
startTethering successful but do not receive following tethering active
notification for a while. Tethering service never actually does anything
synchronously when startTethering is called:
-startProvisioningIfNeeded just posts a message to the handler thread.
-enableTetheringInternal doesn't do anything synchronously, it just
asks the downstreams to get their interfaces ready and waits for
callbacks.
If tethering is already enabled with a different request,
tethering would be disabled and re-enabled.
Bug: 141256482
Test: -build, flash, boot
-atest TetheringTests
-atest CtsTetheringTest
Change-Id: I0399917e7cefa1547d617e688225544c4fc1a231
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 38f8609..5f5de9e 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -34,6 +34,7 @@
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;
@@ -242,6 +243,10 @@
private IDhcpServer mDhcpServer;
private RaParams mLastRaParams;
private LinkAddress mIpv4Address;
+
+ private LinkAddress mStaticIpv4ServerAddr;
+ private LinkAddress mStaticIpv4ClientAddr;
+
@NonNull
private List<TetheredClient> mDhcpLeases = Collections.emptyList();
@@ -544,6 +549,8 @@
// into calls to InterfaceController, shared with startIPv4().
mInterfaceCtrl.clearIPv4Address();
mIpv4Address = null;
+ mStaticIpv4ServerAddr = null;
+ mStaticIpv4ClientAddr = null;
}
private boolean configureIPv4(boolean enabled) {
@@ -554,7 +561,10 @@
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;
@@ -599,10 +609,6 @@
return false;
}
- if (!configureDhcp(enabled, srvAddr, prefixLen)) {
- return false;
- }
-
// Directly-connected route.
final IpPrefix ipv4Prefix = new IpPrefix(mIpv4Address.getAddress(),
mIpv4Address.getPrefixLength());
@@ -614,7 +620,8 @@
mLinkProperties.removeLinkAddress(mIpv4Address);
mLinkProperties.removeRoute(route);
}
- return true;
+
+ return configureDhcp(enabled, srvAddr, prefixLen);
}
private String getRandomWifiIPv4Address() {
@@ -934,6 +941,13 @@
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() {
@@ -948,9 +962,11 @@
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/Tethering/src/android/net/util/TetheringUtils.java b/Tethering/src/android/net/util/TetheringUtils.java
index 5a6d5c1..dd67ddd 100644
--- a/Tethering/src/android/net/util/TetheringUtils.java
+++ b/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 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/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index 5539017..a9e9a75 100644
--- a/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -92,6 +92,7 @@
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 @@
private final SharedLog mLog = new SharedLog(TAG);
private final RemoteCallbackList<ITetheringEventCallback> 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<TetheringRequestParcel> mActiveTetheringRequests =
+ new SparseArray<>();
// used to synchronize public access to members
private final Object mPublicSync;
@@ -487,14 +493,31 @@
}
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 @@
*/
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 @@
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 @@
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 @@
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;
}
}