Merge "[Tether06] Migrate tether offload controller into module"
This commit is contained in:
@@ -27,6 +27,7 @@ java_defaults {
|
||||
"androidx.annotation_annotation",
|
||||
"netd_aidl_interface-java",
|
||||
"networkstack-aidl-interfaces-java",
|
||||
"android.hardware.tetheroffload.control-V1.0-java",
|
||||
"tethering-client",
|
||||
],
|
||||
manifest: "AndroidManifestBase.xml",
|
||||
@@ -38,11 +39,39 @@ android_library {
|
||||
defaults: ["TetheringAndroidLibraryDefaults"],
|
||||
}
|
||||
|
||||
cc_library_shared {
|
||||
name: "libtetheroffloadjni",
|
||||
srcs: [
|
||||
"jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp",
|
||||
],
|
||||
shared_libs: [
|
||||
"libnativehelper",
|
||||
"libcutils",
|
||||
"android.hardware.tetheroffload.config@1.0",
|
||||
],
|
||||
static_libs: [
|
||||
"liblog",
|
||||
"libbase",
|
||||
"libhidlbase",
|
||||
"libutils",
|
||||
],
|
||||
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
"-Wno-unused-parameter",
|
||||
"-Wthread-safety",
|
||||
],
|
||||
}
|
||||
|
||||
// Common defaults for compiling the actual APK.
|
||||
java_defaults {
|
||||
name: "TetheringAppDefaults",
|
||||
platform_apis: true,
|
||||
privileged: true,
|
||||
jni_libs: [
|
||||
"libtetheroffloadjni",
|
||||
],
|
||||
resource_dirs: [
|
||||
"res",
|
||||
],
|
||||
@@ -71,6 +100,8 @@ filegroup {
|
||||
name: "tethering-servicescore-srcs",
|
||||
srcs: [
|
||||
"src/com/android/server/connectivity/tethering/EntitlementManager.java",
|
||||
"src/com/android/server/connectivity/tethering/OffloadController.java",
|
||||
"src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java",
|
||||
"src/com/android/server/connectivity/tethering/TetheringConfiguration.java",
|
||||
"src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java",
|
||||
],
|
||||
@@ -88,3 +119,11 @@ filegroup {
|
||||
"src/android/net/util/PrefixUtils.java",
|
||||
],
|
||||
}
|
||||
|
||||
// This group would be removed when tethering migration is done.
|
||||
filegroup {
|
||||
name: "tethering-jni-srcs",
|
||||
srcs: [
|
||||
"jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <error.h>
|
||||
#include <hidl/HidlSupport.h>
|
||||
#include <jni.h>
|
||||
#include <nativehelper/JNIHelp.h>
|
||||
#include <linux/netfilter/nfnetlink.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <sys/socket.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <android/hardware/tetheroffload/config/1.0/IOffloadConfig.h>
|
||||
|
||||
#define LOG_TAG "OffloadHardwareInterface"
|
||||
#include <utils/Log.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
using hardware::hidl_handle;
|
||||
using hardware::hidl_string;
|
||||
using hardware::tetheroffload::config::V1_0::IOffloadConfig;
|
||||
|
||||
namespace {
|
||||
|
||||
inline const sockaddr * asSockaddr(const sockaddr_nl *nladdr) {
|
||||
return reinterpret_cast<const sockaddr *>(nladdr);
|
||||
}
|
||||
|
||||
int conntrackSocket(unsigned groups) {
|
||||
base::unique_fd s(socket(AF_NETLINK, SOCK_DGRAM, NETLINK_NETFILTER));
|
||||
if (s.get() < 0) return -errno;
|
||||
|
||||
const struct sockaddr_nl bind_addr = {
|
||||
.nl_family = AF_NETLINK,
|
||||
.nl_pad = 0,
|
||||
.nl_pid = 0,
|
||||
.nl_groups = groups,
|
||||
};
|
||||
if (bind(s.get(), asSockaddr(&bind_addr), sizeof(bind_addr)) != 0) {
|
||||
return -errno;
|
||||
}
|
||||
|
||||
const struct sockaddr_nl kernel_addr = {
|
||||
.nl_family = AF_NETLINK,
|
||||
.nl_pad = 0,
|
||||
.nl_pid = 0,
|
||||
.nl_groups = groups,
|
||||
};
|
||||
if (connect(s.get(), asSockaddr(&kernel_addr), sizeof(kernel_addr)) != 0) {
|
||||
return -errno;
|
||||
}
|
||||
|
||||
return s.release();
|
||||
}
|
||||
|
||||
// Return a hidl_handle that owns the file descriptor owned by fd, and will
|
||||
// auto-close it (otherwise there would be double-close problems).
|
||||
//
|
||||
// Rely upon the compiler to eliminate the constexprs used for clarity.
|
||||
hidl_handle handleFromFileDescriptor(base::unique_fd fd) {
|
||||
hidl_handle h;
|
||||
|
||||
static constexpr int kNumFds = 1;
|
||||
static constexpr int kNumInts = 0;
|
||||
native_handle_t *nh = native_handle_create(kNumFds, kNumInts);
|
||||
nh->data[0] = fd.release();
|
||||
|
||||
static constexpr bool kTakeOwnership = true;
|
||||
h.setTo(nh, kTakeOwnership);
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static jboolean android_server_connectivity_tethering_OffloadHardwareInterface_configOffload(
|
||||
JNIEnv* /* env */) {
|
||||
sp<IOffloadConfig> configInterface = IOffloadConfig::getService();
|
||||
if (configInterface.get() == nullptr) {
|
||||
ALOGD("Could not find IOffloadConfig service.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Per the IConfigOffload definition:
|
||||
//
|
||||
// fd1 A file descriptor bound to the following netlink groups
|
||||
// (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY).
|
||||
//
|
||||
// fd2 A file descriptor bound to the following netlink groups
|
||||
// (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
|
||||
base::unique_fd
|
||||
fd1(conntrackSocket(NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY)),
|
||||
fd2(conntrackSocket(NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY));
|
||||
if (fd1.get() < 0 || fd2.get() < 0) {
|
||||
ALOGE("Unable to create conntrack handles: %d/%s", errno, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
hidl_handle h1(handleFromFileDescriptor(std::move(fd1))),
|
||||
h2(handleFromFileDescriptor(std::move(fd2)));
|
||||
|
||||
bool rval(false);
|
||||
hidl_string msg;
|
||||
const auto status = configInterface->setHandles(h1, h2,
|
||||
[&rval, &msg](bool success, const hidl_string& errMsg) {
|
||||
rval = success;
|
||||
msg = errMsg;
|
||||
});
|
||||
if (!status.isOk() || !rval) {
|
||||
ALOGE("IOffloadConfig::setHandles() error: '%s' / '%s'",
|
||||
status.description().c_str(), msg.c_str());
|
||||
// If status is somehow not ok, make sure rval captures this too.
|
||||
rval = false;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
/*
|
||||
* JNI registration.
|
||||
*/
|
||||
static const JNINativeMethod gMethods[] = {
|
||||
/* name, signature, funcPtr */
|
||||
{ "configOffload", "()Z",
|
||||
(void*) android_server_connectivity_tethering_OffloadHardwareInterface_configOffload },
|
||||
};
|
||||
|
||||
int register_android_server_connectivity_tethering_OffloadHardwareInterface(JNIEnv* env) {
|
||||
return jniRegisterNativeMethods(env,
|
||||
"com/android/server/connectivity/tethering/OffloadHardwareInterface",
|
||||
gMethods, NELEM(gMethods));
|
||||
}
|
||||
|
||||
}; // namespace android
|
||||
@@ -0,0 +1,671 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.connectivity.tethering;
|
||||
|
||||
import static android.net.NetworkStats.SET_DEFAULT;
|
||||
import static android.net.NetworkStats.STATS_PER_UID;
|
||||
import static android.net.NetworkStats.TAG_NONE;
|
||||
import static android.net.NetworkStats.UID_ALL;
|
||||
import static android.net.TrafficStats.UID_TETHERING;
|
||||
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.net.ITetheringStatsProvider;
|
||||
import android.net.IpPrefix;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.NetworkStats;
|
||||
import android.net.RouteInfo;
|
||||
import android.net.netlink.ConntrackMessage;
|
||||
import android.net.netlink.NetlinkConstants;
|
||||
import android.net.netlink.NetlinkSocket;
|
||||
import android.net.util.IpUtils;
|
||||
import android.net.util.SharedLog;
|
||||
import android.os.Handler;
|
||||
import android.os.INetworkManagementService;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.Settings;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.OsConstants;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* A class to encapsulate the business logic of programming the tethering
|
||||
* hardware offload interface.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class OffloadController {
|
||||
private static final String TAG = OffloadController.class.getSimpleName();
|
||||
private static final boolean DBG = false;
|
||||
private static final String ANYIP = "0.0.0.0";
|
||||
private static final ForwardedStats EMPTY_STATS = new ForwardedStats();
|
||||
|
||||
private enum UpdateType { IF_NEEDED, FORCE };
|
||||
|
||||
private final Handler mHandler;
|
||||
private final OffloadHardwareInterface mHwInterface;
|
||||
private final ContentResolver mContentResolver;
|
||||
private final INetworkManagementService mNms;
|
||||
private final ITetheringStatsProvider mStatsProvider;
|
||||
private final SharedLog mLog;
|
||||
private final HashMap<String, LinkProperties> mDownstreams;
|
||||
private boolean mConfigInitialized;
|
||||
private boolean mControlInitialized;
|
||||
private LinkProperties mUpstreamLinkProperties;
|
||||
// The complete set of offload-exempt prefixes passed in via Tethering from
|
||||
// all upstream and downstream sources.
|
||||
private Set<IpPrefix> mExemptPrefixes;
|
||||
// A strictly "smaller" set of prefixes, wherein offload-approved prefixes
|
||||
// (e.g. downstream on-link prefixes) have been removed and replaced with
|
||||
// prefixes representing only the locally-assigned IP addresses.
|
||||
private Set<String> mLastLocalPrefixStrs;
|
||||
|
||||
// Maps upstream interface names to offloaded traffic statistics.
|
||||
// Always contains the latest value received from the hardware for each interface, regardless of
|
||||
// whether offload is currently running on that interface.
|
||||
private ConcurrentHashMap<String, ForwardedStats> mForwardedStats =
|
||||
new ConcurrentHashMap<>(16, 0.75F, 1);
|
||||
|
||||
// Maps upstream interface names to interface quotas.
|
||||
// Always contains the latest value received from the framework for each interface, regardless
|
||||
// of whether offload is currently running (or is even supported) on that interface. Only
|
||||
// includes upstream interfaces that have a quota set.
|
||||
private HashMap<String, Long> mInterfaceQuotas = new HashMap<>();
|
||||
|
||||
private int mNatUpdateCallbacksReceived;
|
||||
private int mNatUpdateNetlinkErrors;
|
||||
|
||||
public OffloadController(Handler h, OffloadHardwareInterface hwi,
|
||||
ContentResolver contentResolver, INetworkManagementService nms, SharedLog log) {
|
||||
mHandler = h;
|
||||
mHwInterface = hwi;
|
||||
mContentResolver = contentResolver;
|
||||
mNms = nms;
|
||||
mStatsProvider = new OffloadTetheringStatsProvider();
|
||||
mLog = log.forSubComponent(TAG);
|
||||
mDownstreams = new HashMap<>();
|
||||
mExemptPrefixes = new HashSet<>();
|
||||
mLastLocalPrefixStrs = new HashSet<>();
|
||||
|
||||
try {
|
||||
mNms.registerTetheringStatsProvider(mStatsProvider, getClass().getSimpleName());
|
||||
} catch (RemoteException e) {
|
||||
mLog.e("Cannot register offload stats provider: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Start hardware offload. */
|
||||
public boolean start() {
|
||||
if (started()) return true;
|
||||
|
||||
if (isOffloadDisabled()) {
|
||||
mLog.i("tethering offload disabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mConfigInitialized) {
|
||||
mConfigInitialized = mHwInterface.initOffloadConfig();
|
||||
if (!mConfigInitialized) {
|
||||
mLog.i("tethering offload config not supported");
|
||||
stop();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
mControlInitialized = mHwInterface.initOffloadControl(
|
||||
// OffloadHardwareInterface guarantees that these callback
|
||||
// methods are called on the handler passed to it, which is the
|
||||
// same as mHandler, as coordinated by the setup in Tethering.
|
||||
new OffloadHardwareInterface.ControlCallback() {
|
||||
@Override
|
||||
public void onStarted() {
|
||||
if (!started()) return;
|
||||
mLog.log("onStarted");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoppedError() {
|
||||
if (!started()) return;
|
||||
mLog.log("onStoppedError");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoppedUnsupported() {
|
||||
if (!started()) return;
|
||||
mLog.log("onStoppedUnsupported");
|
||||
// Poll for statistics and trigger a sweep of tethering
|
||||
// stats by observers. This might not succeed, but it's
|
||||
// worth trying anyway. We need to do this because from
|
||||
// this point on we continue with software forwarding,
|
||||
// and we need to synchronize stats and limits between
|
||||
// software and hardware forwarding.
|
||||
updateStatsForAllUpstreams();
|
||||
forceTetherStatsPoll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSupportAvailable() {
|
||||
if (!started()) return;
|
||||
mLog.log("onSupportAvailable");
|
||||
|
||||
// [1] Poll for statistics and trigger a sweep of stats
|
||||
// by observers. We need to do this to ensure that any
|
||||
// limits set take into account any software tethering
|
||||
// traffic that has been happening in the meantime.
|
||||
updateStatsForAllUpstreams();
|
||||
forceTetherStatsPoll();
|
||||
// [2] (Re)Push all state.
|
||||
computeAndPushLocalPrefixes(UpdateType.FORCE);
|
||||
pushAllDownstreamState();
|
||||
pushUpstreamParameters(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoppedLimitReached() {
|
||||
if (!started()) return;
|
||||
mLog.log("onStoppedLimitReached");
|
||||
|
||||
// We cannot reliably determine on which interface the limit was reached,
|
||||
// because the HAL interface does not specify it. We cannot just use the
|
||||
// current upstream, because that might have changed since the time that
|
||||
// the HAL queued the callback.
|
||||
// TODO: rev the HAL so that it provides an interface name.
|
||||
|
||||
// Fetch current stats, so that when our notification reaches
|
||||
// NetworkStatsService and triggers a poll, we will respond with
|
||||
// current data (which will be above the limit that was reached).
|
||||
// Note that if we just changed upstream, this is unnecessary but harmless.
|
||||
// The stats for the previous upstream were already updated on this thread
|
||||
// just after the upstream was changed, so they are also up-to-date.
|
||||
updateStatsForCurrentUpstream();
|
||||
forceTetherStatsPoll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNatTimeoutUpdate(int proto,
|
||||
String srcAddr, int srcPort,
|
||||
String dstAddr, int dstPort) {
|
||||
if (!started()) return;
|
||||
updateNatTimeout(proto, srcAddr, srcPort, dstAddr, dstPort);
|
||||
}
|
||||
});
|
||||
|
||||
final boolean isStarted = started();
|
||||
if (!isStarted) {
|
||||
mLog.i("tethering offload control not supported");
|
||||
stop();
|
||||
} else {
|
||||
mLog.log("tethering offload started");
|
||||
mNatUpdateCallbacksReceived = 0;
|
||||
mNatUpdateNetlinkErrors = 0;
|
||||
}
|
||||
return isStarted;
|
||||
}
|
||||
|
||||
/** Stop hardware offload. */
|
||||
public void stop() {
|
||||
// Completely stops tethering offload. After this method is called, it is no longer safe to
|
||||
// call any HAL method, no callbacks from the hardware will be delivered, and any in-flight
|
||||
// callbacks must be ignored. Offload may be started again by calling start().
|
||||
final boolean wasStarted = started();
|
||||
updateStatsForCurrentUpstream();
|
||||
mUpstreamLinkProperties = null;
|
||||
mHwInterface.stopOffloadControl();
|
||||
mControlInitialized = false;
|
||||
mConfigInitialized = false;
|
||||
if (wasStarted) mLog.log("tethering offload stopped");
|
||||
}
|
||||
|
||||
private boolean started() {
|
||||
return mConfigInitialized && mControlInitialized;
|
||||
}
|
||||
|
||||
private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub {
|
||||
@Override
|
||||
public NetworkStats getTetherStats(int how) {
|
||||
// getTetherStats() is the only function in OffloadController that can be called from
|
||||
// a different thread. Do not attempt to update stats by querying the offload HAL
|
||||
// synchronously from a different thread than our Handler thread. http://b/64771555.
|
||||
Runnable updateStats = () -> {
|
||||
updateStatsForCurrentUpstream();
|
||||
};
|
||||
if (Looper.myLooper() == mHandler.getLooper()) {
|
||||
updateStats.run();
|
||||
} else {
|
||||
mHandler.post(updateStats);
|
||||
}
|
||||
|
||||
NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
|
||||
NetworkStats.Entry entry = new NetworkStats.Entry();
|
||||
entry.set = SET_DEFAULT;
|
||||
entry.tag = TAG_NONE;
|
||||
entry.uid = (how == STATS_PER_UID) ? UID_TETHERING : UID_ALL;
|
||||
|
||||
for (Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) {
|
||||
ForwardedStats value = kv.getValue();
|
||||
entry.iface = kv.getKey();
|
||||
entry.rxBytes = value.rxBytes;
|
||||
entry.txBytes = value.txBytes;
|
||||
stats.addValues(entry);
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInterfaceQuota(String iface, long quotaBytes) {
|
||||
mHandler.post(() -> {
|
||||
if (quotaBytes == ITetheringStatsProvider.QUOTA_UNLIMITED) {
|
||||
mInterfaceQuotas.remove(iface);
|
||||
} else {
|
||||
mInterfaceQuotas.put(iface, quotaBytes);
|
||||
}
|
||||
maybeUpdateDataLimit(iface);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private String currentUpstreamInterface() {
|
||||
return (mUpstreamLinkProperties != null)
|
||||
? mUpstreamLinkProperties.getInterfaceName() : null;
|
||||
}
|
||||
|
||||
private void maybeUpdateStats(String iface) {
|
||||
if (TextUtils.isEmpty(iface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Always called on the handler thread.
|
||||
//
|
||||
// Use get()/put() instead of updating ForwardedStats in place because we can be called
|
||||
// concurrently with getTetherStats. In combination with the guarantees provided by
|
||||
// ConcurrentHashMap, this ensures that getTetherStats always gets the most recent copy of
|
||||
// the stats for each interface, and does not observe partial writes where rxBytes is
|
||||
// updated and txBytes is not.
|
||||
ForwardedStats diff = mHwInterface.getForwardedStats(iface);
|
||||
ForwardedStats base = mForwardedStats.get(iface);
|
||||
if (base != null) {
|
||||
diff.add(base);
|
||||
}
|
||||
mForwardedStats.put(iface, diff);
|
||||
// diff is a new object, just created by getForwardedStats(). Therefore, anyone reading from
|
||||
// mForwardedStats (i.e., any caller of getTetherStats) will see the new stats immediately.
|
||||
}
|
||||
|
||||
private boolean maybeUpdateDataLimit(String iface) {
|
||||
// setDataLimit may only be called while offload is occurring on this upstream.
|
||||
if (!started() || !TextUtils.equals(iface, currentUpstreamInterface())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Long limit = mInterfaceQuotas.get(iface);
|
||||
if (limit == null) {
|
||||
limit = Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
return mHwInterface.setDataLimit(iface, limit);
|
||||
}
|
||||
|
||||
private void updateStatsForCurrentUpstream() {
|
||||
maybeUpdateStats(currentUpstreamInterface());
|
||||
}
|
||||
|
||||
private void updateStatsForAllUpstreams() {
|
||||
// In practice, there should only ever be a single digit number of
|
||||
// upstream interfaces over the lifetime of an active tethering session.
|
||||
// Roughly speaking, imagine a very ambitious one or two of each of the
|
||||
// following interface types: [ "rmnet_data", "wlan", "eth", "rndis" ].
|
||||
for (Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) {
|
||||
maybeUpdateStats(kv.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
private void forceTetherStatsPoll() {
|
||||
try {
|
||||
mNms.tetherLimitReached(mStatsProvider);
|
||||
} catch (RemoteException e) {
|
||||
mLog.e("Cannot report data limit reached: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Set current tethering upstream LinkProperties. */
|
||||
public void setUpstreamLinkProperties(LinkProperties lp) {
|
||||
if (!started() || Objects.equals(mUpstreamLinkProperties, lp)) return;
|
||||
|
||||
final String prevUpstream = currentUpstreamInterface();
|
||||
|
||||
mUpstreamLinkProperties = (lp != null) ? new LinkProperties(lp) : null;
|
||||
// Make sure we record this interface in the ForwardedStats map.
|
||||
final String iface = currentUpstreamInterface();
|
||||
if (!TextUtils.isEmpty(iface)) mForwardedStats.putIfAbsent(iface, EMPTY_STATS);
|
||||
|
||||
// TODO: examine return code and decide what to do if programming
|
||||
// upstream parameters fails (probably just wait for a subsequent
|
||||
// onOffloadEvent() callback to tell us offload is available again and
|
||||
// then reapply all state).
|
||||
computeAndPushLocalPrefixes(UpdateType.IF_NEEDED);
|
||||
pushUpstreamParameters(prevUpstream);
|
||||
}
|
||||
|
||||
/** Set local prefixes. */
|
||||
public void setLocalPrefixes(Set<IpPrefix> localPrefixes) {
|
||||
mExemptPrefixes = localPrefixes;
|
||||
|
||||
if (!started()) return;
|
||||
computeAndPushLocalPrefixes(UpdateType.IF_NEEDED);
|
||||
}
|
||||
|
||||
/** Update current downstream LinkProperties. */
|
||||
public void notifyDownstreamLinkProperties(LinkProperties lp) {
|
||||
final String ifname = lp.getInterfaceName();
|
||||
final LinkProperties oldLp = mDownstreams.put(ifname, new LinkProperties(lp));
|
||||
if (Objects.equals(oldLp, lp)) return;
|
||||
|
||||
if (!started()) return;
|
||||
pushDownstreamState(oldLp, lp);
|
||||
}
|
||||
|
||||
private void pushDownstreamState(LinkProperties oldLp, LinkProperties newLp) {
|
||||
final String ifname = newLp.getInterfaceName();
|
||||
final List<RouteInfo> oldRoutes =
|
||||
(oldLp != null) ? oldLp.getRoutes() : Collections.EMPTY_LIST;
|
||||
final List<RouteInfo> newRoutes = newLp.getRoutes();
|
||||
|
||||
// For each old route, if not in new routes: remove.
|
||||
for (RouteInfo ri : oldRoutes) {
|
||||
if (shouldIgnoreDownstreamRoute(ri)) continue;
|
||||
if (!newRoutes.contains(ri)) {
|
||||
mHwInterface.removeDownstreamPrefix(ifname, ri.getDestination().toString());
|
||||
}
|
||||
}
|
||||
|
||||
// For each new route, if not in old routes: add.
|
||||
for (RouteInfo ri : newRoutes) {
|
||||
if (shouldIgnoreDownstreamRoute(ri)) continue;
|
||||
if (!oldRoutes.contains(ri)) {
|
||||
mHwInterface.addDownstreamPrefix(ifname, ri.getDestination().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void pushAllDownstreamState() {
|
||||
for (LinkProperties lp : mDownstreams.values()) {
|
||||
pushDownstreamState(null, lp);
|
||||
}
|
||||
}
|
||||
|
||||
/** Remove downstream interface from offload hardware. */
|
||||
public void removeDownstreamInterface(String ifname) {
|
||||
final LinkProperties lp = mDownstreams.remove(ifname);
|
||||
if (lp == null) return;
|
||||
|
||||
if (!started()) return;
|
||||
|
||||
for (RouteInfo route : lp.getRoutes()) {
|
||||
if (shouldIgnoreDownstreamRoute(route)) continue;
|
||||
mHwInterface.removeDownstreamPrefix(ifname, route.getDestination().toString());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isOffloadDisabled() {
|
||||
final int defaultDisposition = mHwInterface.getDefaultTetherOffloadDisabled();
|
||||
return (Settings.Global.getInt(
|
||||
mContentResolver, TETHER_OFFLOAD_DISABLED, defaultDisposition) != 0);
|
||||
}
|
||||
|
||||
private boolean pushUpstreamParameters(String prevUpstream) {
|
||||
final String iface = currentUpstreamInterface();
|
||||
|
||||
if (TextUtils.isEmpty(iface)) {
|
||||
final boolean rval = mHwInterface.setUpstreamParameters("", ANYIP, ANYIP, null);
|
||||
// Update stats after we've told the hardware to stop forwarding so
|
||||
// we don't miss packets.
|
||||
maybeUpdateStats(prevUpstream);
|
||||
return rval;
|
||||
}
|
||||
|
||||
// A stacked interface cannot be an upstream for hardware offload.
|
||||
// Consequently, we examine only the primary interface name, look at
|
||||
// getAddresses() rather than getAllAddresses(), and check getRoutes()
|
||||
// rather than getAllRoutes().
|
||||
final ArrayList<String> v6gateways = new ArrayList<>();
|
||||
String v4addr = null;
|
||||
String v4gateway = null;
|
||||
|
||||
for (InetAddress ip : mUpstreamLinkProperties.getAddresses()) {
|
||||
if (ip instanceof Inet4Address) {
|
||||
v4addr = ip.getHostAddress();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the gateway addresses of all default routes of either address family.
|
||||
for (RouteInfo ri : mUpstreamLinkProperties.getRoutes()) {
|
||||
if (!ri.hasGateway()) continue;
|
||||
|
||||
final String gateway = ri.getGateway().getHostAddress();
|
||||
if (ri.isIPv4Default()) {
|
||||
v4gateway = gateway;
|
||||
} else if (ri.isIPv6Default()) {
|
||||
v6gateways.add(gateway);
|
||||
}
|
||||
}
|
||||
|
||||
boolean success = mHwInterface.setUpstreamParameters(
|
||||
iface, v4addr, v4gateway, (v6gateways.isEmpty() ? null : v6gateways));
|
||||
|
||||
if (!success) {
|
||||
return success;
|
||||
}
|
||||
|
||||
// Update stats after we've told the hardware to change routing so we don't miss packets.
|
||||
maybeUpdateStats(prevUpstream);
|
||||
|
||||
// Data limits can only be set once offload is running on the upstream.
|
||||
success = maybeUpdateDataLimit(iface);
|
||||
if (!success) {
|
||||
// If we failed to set a data limit, don't use this upstream, because we don't want to
|
||||
// blow through the data limit that we were told to apply.
|
||||
mLog.log("Setting data limit for " + iface + " failed, disabling offload.");
|
||||
stop();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private boolean computeAndPushLocalPrefixes(UpdateType how) {
|
||||
final boolean force = (how == UpdateType.FORCE);
|
||||
final Set<String> localPrefixStrs = computeLocalPrefixStrings(
|
||||
mExemptPrefixes, mUpstreamLinkProperties);
|
||||
if (!force && mLastLocalPrefixStrs.equals(localPrefixStrs)) return true;
|
||||
|
||||
mLastLocalPrefixStrs = localPrefixStrs;
|
||||
return mHwInterface.setLocalPrefixes(new ArrayList<>(localPrefixStrs));
|
||||
}
|
||||
|
||||
// TODO: Factor in downstream LinkProperties once that information is available.
|
||||
private static Set<String> computeLocalPrefixStrings(
|
||||
Set<IpPrefix> localPrefixes, LinkProperties upstreamLinkProperties) {
|
||||
// Create an editable copy.
|
||||
final Set<IpPrefix> prefixSet = new HashSet<>(localPrefixes);
|
||||
|
||||
// TODO: If a downstream interface (not currently passed in) is reusing
|
||||
// the /64 of the upstream (64share) then:
|
||||
//
|
||||
// [a] remove that /64 from the local prefixes
|
||||
// [b] add in /128s for IP addresses on the downstream interface
|
||||
// [c] add in /128s for IP addresses on the upstream interface
|
||||
//
|
||||
// Until downstream information is available here, simply add /128s from
|
||||
// the upstream network; they'll just be redundant with their /64.
|
||||
if (upstreamLinkProperties != null) {
|
||||
for (LinkAddress linkAddr : upstreamLinkProperties.getLinkAddresses()) {
|
||||
if (!linkAddr.isGlobalPreferred()) continue;
|
||||
final InetAddress ip = linkAddr.getAddress();
|
||||
if (!(ip instanceof Inet6Address)) continue;
|
||||
prefixSet.add(new IpPrefix(ip, 128));
|
||||
}
|
||||
}
|
||||
|
||||
final HashSet<String> localPrefixStrs = new HashSet<>();
|
||||
for (IpPrefix pfx : prefixSet) localPrefixStrs.add(pfx.toString());
|
||||
return localPrefixStrs;
|
||||
}
|
||||
|
||||
private static boolean shouldIgnoreDownstreamRoute(RouteInfo route) {
|
||||
// Ignore any link-local routes.
|
||||
if (!route.getDestinationLinkAddress().isGlobalPreferred()) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Dump information. */
|
||||
public void dump(IndentingPrintWriter pw) {
|
||||
if (isOffloadDisabled()) {
|
||||
pw.println("Offload disabled");
|
||||
return;
|
||||
}
|
||||
final boolean isStarted = started();
|
||||
pw.println("Offload HALs " + (isStarted ? "started" : "not started"));
|
||||
LinkProperties lp = mUpstreamLinkProperties;
|
||||
String upstream = (lp != null) ? lp.getInterfaceName() : null;
|
||||
pw.println("Current upstream: " + upstream);
|
||||
pw.println("Exempt prefixes: " + mLastLocalPrefixStrs);
|
||||
pw.println("NAT timeout update callbacks received during the "
|
||||
+ (isStarted ? "current" : "last")
|
||||
+ " offload session: "
|
||||
+ mNatUpdateCallbacksReceived);
|
||||
pw.println("NAT timeout update netlink errors during the "
|
||||
+ (isStarted ? "current" : "last")
|
||||
+ " offload session: "
|
||||
+ mNatUpdateNetlinkErrors);
|
||||
}
|
||||
|
||||
private void updateNatTimeout(
|
||||
int proto, String srcAddr, int srcPort, String dstAddr, int dstPort) {
|
||||
final String protoName = protoNameFor(proto);
|
||||
if (protoName == null) {
|
||||
mLog.e("Unknown NAT update callback protocol: " + proto);
|
||||
return;
|
||||
}
|
||||
|
||||
final Inet4Address src = parseIPv4Address(srcAddr);
|
||||
if (src == null) {
|
||||
mLog.e("Failed to parse IPv4 address: " + srcAddr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IpUtils.isValidUdpOrTcpPort(srcPort)) {
|
||||
mLog.e("Invalid src port: " + srcPort);
|
||||
return;
|
||||
}
|
||||
|
||||
final Inet4Address dst = parseIPv4Address(dstAddr);
|
||||
if (dst == null) {
|
||||
mLog.e("Failed to parse IPv4 address: " + dstAddr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IpUtils.isValidUdpOrTcpPort(dstPort)) {
|
||||
mLog.e("Invalid dst port: " + dstPort);
|
||||
return;
|
||||
}
|
||||
|
||||
mNatUpdateCallbacksReceived++;
|
||||
final String natDescription = String.format("%s (%s, %s) -> (%s, %s)",
|
||||
protoName, srcAddr, srcPort, dstAddr, dstPort);
|
||||
if (DBG) {
|
||||
mLog.log("NAT timeout update: " + natDescription);
|
||||
}
|
||||
|
||||
final int timeoutSec = connectionTimeoutUpdateSecondsFor(proto);
|
||||
final byte[] msg = ConntrackMessage.newIPv4TimeoutUpdateRequest(
|
||||
proto, src, srcPort, dst, dstPort, timeoutSec);
|
||||
|
||||
try {
|
||||
NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_NETFILTER, msg);
|
||||
} catch (ErrnoException e) {
|
||||
mNatUpdateNetlinkErrors++;
|
||||
mLog.e("Error updating NAT conntrack entry >" + natDescription + "<: " + e
|
||||
+ ", msg: " + NetlinkConstants.hexify(msg));
|
||||
mLog.log("NAT timeout update callbacks received: " + mNatUpdateCallbacksReceived);
|
||||
mLog.log("NAT timeout update netlink errors: " + mNatUpdateNetlinkErrors);
|
||||
}
|
||||
}
|
||||
|
||||
private static Inet4Address parseIPv4Address(String addrString) {
|
||||
try {
|
||||
final InetAddress ip = InetAddress.parseNumericAddress(addrString);
|
||||
// TODO: Consider other sanitization steps here, including perhaps:
|
||||
// not eql to 0.0.0.0
|
||||
// not within 169.254.0.0/16
|
||||
// not within ::ffff:0.0.0.0/96
|
||||
// not within ::/96
|
||||
// et cetera.
|
||||
if (ip instanceof Inet4Address) {
|
||||
return (Inet4Address) ip;
|
||||
}
|
||||
} catch (IllegalArgumentException iae) { }
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String protoNameFor(int proto) {
|
||||
// OsConstants values are not constant expressions; no switch statement.
|
||||
if (proto == OsConstants.IPPROTO_UDP) {
|
||||
return "UDP";
|
||||
} else if (proto == OsConstants.IPPROTO_TCP) {
|
||||
return "TCP";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int connectionTimeoutUpdateSecondsFor(int proto) {
|
||||
// TODO: Replace this with more thoughtful work, perhaps reading from
|
||||
// and maybe writing to any required
|
||||
//
|
||||
// /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_*
|
||||
// /proc/sys/net/netfilter/nf_conntrack_udp_timeout{,_stream}
|
||||
//
|
||||
// entries. TBD.
|
||||
if (proto == OsConstants.IPPROTO_TCP) {
|
||||
// Cf. /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established
|
||||
return 432000;
|
||||
} else {
|
||||
// Cf. /proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream
|
||||
return 180;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.connectivity.tethering;
|
||||
|
||||
import static com.android.internal.util.BitUtils.uint16;
|
||||
|
||||
import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
|
||||
import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
|
||||
import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
|
||||
import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
|
||||
import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
|
||||
import android.net.util.SharedLog;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.system.OsConstants;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
/**
|
||||
* Capture tethering dependencies, for injection.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class OffloadHardwareInterface {
|
||||
private static final String TAG = OffloadHardwareInterface.class.getSimpleName();
|
||||
private static final String YIELDS = " -> ";
|
||||
// Change this value to control whether tether offload is enabled or
|
||||
// disabled by default in the absence of an explicit Settings value.
|
||||
// See accompanying unittest to distinguish 0 from non-0 values.
|
||||
private static final int DEFAULT_TETHER_OFFLOAD_DISABLED = 0;
|
||||
private static final String NO_INTERFACE_NAME = "";
|
||||
private static final String NO_IPV4_ADDRESS = "";
|
||||
private static final String NO_IPV4_GATEWAY = "";
|
||||
|
||||
private static native boolean configOffload();
|
||||
|
||||
private final Handler mHandler;
|
||||
private final SharedLog mLog;
|
||||
private IOffloadControl mOffloadControl;
|
||||
private TetheringOffloadCallback mTetheringOffloadCallback;
|
||||
private ControlCallback mControlCallback;
|
||||
|
||||
/** The callback to notify status of offload management process. */
|
||||
public static class ControlCallback {
|
||||
/** Offload started. */
|
||||
public void onStarted() {}
|
||||
/**
|
||||
* Offload stopped because an error has occurred in lower layer.
|
||||
*/
|
||||
public void onStoppedError() {}
|
||||
/**
|
||||
* Offload stopped because the device has moved to a bearer on which hardware offload is
|
||||
* not supported. Subsequent calls to setUpstreamParameters and add/removeDownstream will
|
||||
* likely fail and cannot be presumed to be saved inside of the hardware management process.
|
||||
* Upon receiving #onSupportAvailable(), the caller should reprogram the hardware to begin
|
||||
* offload again.
|
||||
*/
|
||||
public void onStoppedUnsupported() {}
|
||||
/** Indicate that offload is able to proivde support for this time. */
|
||||
public void onSupportAvailable() {}
|
||||
/** Offload stopped because of usage limit reached. */
|
||||
public void onStoppedLimitReached() {}
|
||||
|
||||
/** Indicate to update NAT timeout. */
|
||||
public void onNatTimeoutUpdate(int proto,
|
||||
String srcAddr, int srcPort,
|
||||
String dstAddr, int dstPort) {}
|
||||
}
|
||||
|
||||
/** The object which records Tx/Rx forwarded bytes. */
|
||||
public static class ForwardedStats {
|
||||
public long rxBytes;
|
||||
public long txBytes;
|
||||
|
||||
public ForwardedStats() {
|
||||
rxBytes = 0;
|
||||
txBytes = 0;
|
||||
}
|
||||
|
||||
/** Add Tx/Rx bytes. */
|
||||
public void add(ForwardedStats other) {
|
||||
rxBytes += other.rxBytes;
|
||||
txBytes += other.txBytes;
|
||||
}
|
||||
|
||||
/** Returns the string representation of this object. */
|
||||
public String toString() {
|
||||
return String.format("rx:%s tx:%s", rxBytes, txBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public OffloadHardwareInterface(Handler h, SharedLog log) {
|
||||
mHandler = h;
|
||||
mLog = log.forSubComponent(TAG);
|
||||
}
|
||||
|
||||
/** Get default value indicating whether offload is supported. */
|
||||
public int getDefaultTetherOffloadDisabled() {
|
||||
return DEFAULT_TETHER_OFFLOAD_DISABLED;
|
||||
}
|
||||
|
||||
/** Configure offload management process. */
|
||||
public boolean initOffloadConfig() {
|
||||
return configOffload();
|
||||
}
|
||||
|
||||
/** Initialize the tethering offload HAL. */
|
||||
public boolean initOffloadControl(ControlCallback controlCb) {
|
||||
mControlCallback = controlCb;
|
||||
|
||||
if (mOffloadControl == null) {
|
||||
try {
|
||||
mOffloadControl = IOffloadControl.getService();
|
||||
} catch (RemoteException e) {
|
||||
mLog.e("tethering offload control not supported: " + e);
|
||||
return false;
|
||||
}
|
||||
if (mOffloadControl == null) {
|
||||
mLog.e("tethering IOffloadControl.getService() returned null");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final String logmsg = String.format("initOffloadControl(%s)",
|
||||
(controlCb == null) ? "null"
|
||||
: "0x" + Integer.toHexString(System.identityHashCode(controlCb)));
|
||||
|
||||
mTetheringOffloadCallback = new TetheringOffloadCallback(mHandler, mControlCallback, mLog);
|
||||
final CbResults results = new CbResults();
|
||||
try {
|
||||
mOffloadControl.initOffload(
|
||||
mTetheringOffloadCallback,
|
||||
(boolean success, String errMsg) -> {
|
||||
results.mSuccess = success;
|
||||
results.mErrMsg = errMsg;
|
||||
});
|
||||
} catch (RemoteException e) {
|
||||
record(logmsg, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
record(logmsg, results);
|
||||
return results.mSuccess;
|
||||
}
|
||||
|
||||
/** Stop IOffloadControl. */
|
||||
public void stopOffloadControl() {
|
||||
if (mOffloadControl != null) {
|
||||
try {
|
||||
mOffloadControl.stopOffload(
|
||||
(boolean success, String errMsg) -> {
|
||||
if (!success) mLog.e("stopOffload failed: " + errMsg);
|
||||
});
|
||||
} catch (RemoteException e) {
|
||||
mLog.e("failed to stopOffload: " + e);
|
||||
}
|
||||
}
|
||||
mOffloadControl = null;
|
||||
mTetheringOffloadCallback = null;
|
||||
mControlCallback = null;
|
||||
mLog.log("stopOffloadControl()");
|
||||
}
|
||||
|
||||
/** Get Tx/Rx usage from last query. */
|
||||
public ForwardedStats getForwardedStats(String upstream) {
|
||||
final String logmsg = String.format("getForwardedStats(%s)", upstream);
|
||||
|
||||
final ForwardedStats stats = new ForwardedStats();
|
||||
try {
|
||||
mOffloadControl.getForwardedStats(
|
||||
upstream,
|
||||
(long rxBytes, long txBytes) -> {
|
||||
stats.rxBytes = (rxBytes > 0) ? rxBytes : 0;
|
||||
stats.txBytes = (txBytes > 0) ? txBytes : 0;
|
||||
});
|
||||
} catch (RemoteException e) {
|
||||
record(logmsg, e);
|
||||
return stats;
|
||||
}
|
||||
|
||||
mLog.log(logmsg + YIELDS + stats);
|
||||
return stats;
|
||||
}
|
||||
|
||||
/** Set local prefixes to offload management process. */
|
||||
public boolean setLocalPrefixes(ArrayList<String> localPrefixes) {
|
||||
final String logmsg = String.format("setLocalPrefixes([%s])",
|
||||
String.join(",", localPrefixes));
|
||||
|
||||
final CbResults results = new CbResults();
|
||||
try {
|
||||
mOffloadControl.setLocalPrefixes(localPrefixes,
|
||||
(boolean success, String errMsg) -> {
|
||||
results.mSuccess = success;
|
||||
results.mErrMsg = errMsg;
|
||||
});
|
||||
} catch (RemoteException e) {
|
||||
record(logmsg, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
record(logmsg, results);
|
||||
return results.mSuccess;
|
||||
}
|
||||
|
||||
/** Set data limit value to offload management process. */
|
||||
public boolean setDataLimit(String iface, long limit) {
|
||||
|
||||
final String logmsg = String.format("setDataLimit(%s, %d)", iface, limit);
|
||||
|
||||
final CbResults results = new CbResults();
|
||||
try {
|
||||
mOffloadControl.setDataLimit(
|
||||
iface, limit,
|
||||
(boolean success, String errMsg) -> {
|
||||
results.mSuccess = success;
|
||||
results.mErrMsg = errMsg;
|
||||
});
|
||||
} catch (RemoteException e) {
|
||||
record(logmsg, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
record(logmsg, results);
|
||||
return results.mSuccess;
|
||||
}
|
||||
|
||||
/** Set upstream parameters to offload management process. */
|
||||
public boolean setUpstreamParameters(
|
||||
String iface, String v4addr, String v4gateway, ArrayList<String> v6gws) {
|
||||
iface = (iface != null) ? iface : NO_INTERFACE_NAME;
|
||||
v4addr = (v4addr != null) ? v4addr : NO_IPV4_ADDRESS;
|
||||
v4gateway = (v4gateway != null) ? v4gateway : NO_IPV4_GATEWAY;
|
||||
v6gws = (v6gws != null) ? v6gws : new ArrayList<>();
|
||||
|
||||
final String logmsg = String.format("setUpstreamParameters(%s, %s, %s, [%s])",
|
||||
iface, v4addr, v4gateway, String.join(",", v6gws));
|
||||
|
||||
final CbResults results = new CbResults();
|
||||
try {
|
||||
mOffloadControl.setUpstreamParameters(
|
||||
iface, v4addr, v4gateway, v6gws,
|
||||
(boolean success, String errMsg) -> {
|
||||
results.mSuccess = success;
|
||||
results.mErrMsg = errMsg;
|
||||
});
|
||||
} catch (RemoteException e) {
|
||||
record(logmsg, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
record(logmsg, results);
|
||||
return results.mSuccess;
|
||||
}
|
||||
|
||||
/** Add downstream prefix to offload management process. */
|
||||
public boolean addDownstreamPrefix(String ifname, String prefix) {
|
||||
final String logmsg = String.format("addDownstreamPrefix(%s, %s)", ifname, prefix);
|
||||
|
||||
final CbResults results = new CbResults();
|
||||
try {
|
||||
mOffloadControl.addDownstream(ifname, prefix,
|
||||
(boolean success, String errMsg) -> {
|
||||
results.mSuccess = success;
|
||||
results.mErrMsg = errMsg;
|
||||
});
|
||||
} catch (RemoteException e) {
|
||||
record(logmsg, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
record(logmsg, results);
|
||||
return results.mSuccess;
|
||||
}
|
||||
|
||||
/** Remove downstream prefix from offload management process. */
|
||||
public boolean removeDownstreamPrefix(String ifname, String prefix) {
|
||||
final String logmsg = String.format("removeDownstreamPrefix(%s, %s)", ifname, prefix);
|
||||
|
||||
final CbResults results = new CbResults();
|
||||
try {
|
||||
mOffloadControl.removeDownstream(ifname, prefix,
|
||||
(boolean success, String errMsg) -> {
|
||||
results.mSuccess = success;
|
||||
results.mErrMsg = errMsg;
|
||||
});
|
||||
} catch (RemoteException e) {
|
||||
record(logmsg, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
record(logmsg, results);
|
||||
return results.mSuccess;
|
||||
}
|
||||
|
||||
private void record(String msg, Throwable t) {
|
||||
mLog.e(msg + YIELDS + "exception: " + t);
|
||||
}
|
||||
|
||||
private void record(String msg, CbResults results) {
|
||||
final String logmsg = msg + YIELDS + results;
|
||||
if (!results.mSuccess) {
|
||||
mLog.e(logmsg);
|
||||
} else {
|
||||
mLog.log(logmsg);
|
||||
}
|
||||
}
|
||||
|
||||
private static class TetheringOffloadCallback extends ITetheringOffloadCallback.Stub {
|
||||
public final Handler handler;
|
||||
public final ControlCallback controlCb;
|
||||
public final SharedLog log;
|
||||
|
||||
TetheringOffloadCallback(Handler h, ControlCallback cb, SharedLog sharedLog) {
|
||||
handler = h;
|
||||
controlCb = cb;
|
||||
log = sharedLog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(int event) {
|
||||
handler.post(() -> {
|
||||
switch (event) {
|
||||
case OffloadCallbackEvent.OFFLOAD_STARTED:
|
||||
controlCb.onStarted();
|
||||
break;
|
||||
case OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR:
|
||||
controlCb.onStoppedError();
|
||||
break;
|
||||
case OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED:
|
||||
controlCb.onStoppedUnsupported();
|
||||
break;
|
||||
case OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE:
|
||||
controlCb.onSupportAvailable();
|
||||
break;
|
||||
case OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED:
|
||||
controlCb.onStoppedLimitReached();
|
||||
break;
|
||||
default:
|
||||
log.e("Unsupported OffloadCallbackEvent: " + event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTimeout(NatTimeoutUpdate params) {
|
||||
handler.post(() -> {
|
||||
controlCb.onNatTimeoutUpdate(
|
||||
networkProtocolToOsConstant(params.proto),
|
||||
params.src.addr, uint16(params.src.port),
|
||||
params.dst.addr, uint16(params.dst.port));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static int networkProtocolToOsConstant(int proto) {
|
||||
switch (proto) {
|
||||
case NetworkProtocol.TCP: return OsConstants.IPPROTO_TCP;
|
||||
case NetworkProtocol.UDP: return OsConstants.IPPROTO_UDP;
|
||||
default:
|
||||
// The caller checks this value and will log an error. Just make
|
||||
// sure it won't collide with valid OsContants.IPPROTO_* values.
|
||||
return -Math.abs(proto);
|
||||
}
|
||||
}
|
||||
|
||||
private static class CbResults {
|
||||
boolean mSuccess;
|
||||
String mErrMsg;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (mSuccess) {
|
||||
return "ok";
|
||||
} else {
|
||||
return "fail: " + mErrMsg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ android_test {
|
||||
static_libs: [
|
||||
"androidx.test.rules",
|
||||
"frameworks-base-testutils",
|
||||
"net-tests-utils",
|
||||
"mockito-target-extended-minus-junit4",
|
||||
"TetheringApiCurrentLib",
|
||||
"testables",
|
||||
@@ -46,6 +47,7 @@ filegroup {
|
||||
name: "tethering-tests-src",
|
||||
srcs: [
|
||||
"src/com/android/server/connectivity/tethering/EntitlementManagerTest.java",
|
||||
"src/com/android/server/connectivity/tethering/OffloadControllerTest.java",
|
||||
"src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java",
|
||||
"src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java",
|
||||
"src/android/net/dhcp/DhcpServingParamsParcelExtTest.java",
|
||||
|
||||
@@ -0,0 +1,730 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.connectivity.tethering;
|
||||
|
||||
import static android.net.NetworkStats.SET_DEFAULT;
|
||||
import static android.net.NetworkStats.STATS_PER_IFACE;
|
||||
import static android.net.NetworkStats.STATS_PER_UID;
|
||||
import static android.net.NetworkStats.TAG_NONE;
|
||||
import static android.net.NetworkStats.UID_ALL;
|
||||
import static android.net.TrafficStats.UID_TETHERING;
|
||||
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
|
||||
|
||||
import static com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
|
||||
import static com.android.testutils.MiscAssertsKt.assertContainsAll;
|
||||
import static com.android.testutils.MiscAssertsKt.assertThrows;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyLong;
|
||||
import static org.mockito.Matchers.anyObject;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.net.ITetheringStatsProvider;
|
||||
import android.net.IpPrefix;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.NetworkStats;
|
||||
import android.net.RouteInfo;
|
||||
import android.net.util.SharedLog;
|
||||
import android.os.Handler;
|
||||
import android.os.INetworkManagementService;
|
||||
import android.os.Looper;
|
||||
import android.provider.Settings;
|
||||
import android.provider.Settings.SettingNotFoundException;
|
||||
import android.test.mock.MockContentResolver;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.util.test.FakeSettingsProvider;
|
||||
import com.android.testutils.HandlerUtilsKt;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class OffloadControllerTest {
|
||||
private static final String RNDIS0 = "test_rndis0";
|
||||
private static final String RMNET0 = "test_rmnet_data0";
|
||||
private static final String WLAN0 = "test_wlan0";
|
||||
|
||||
private static final String IPV6_LINKLOCAL = "fe80::/64";
|
||||
private static final String IPV6_DOC_PREFIX = "2001:db8::/64";
|
||||
private static final String IPV6_DISCARD_PREFIX = "100::/64";
|
||||
private static final String USB_PREFIX = "192.168.42.0/24";
|
||||
private static final String WIFI_PREFIX = "192.168.43.0/24";
|
||||
private static final long WAIT_FOR_IDLE_TIMEOUT = 2 * 1000;
|
||||
|
||||
@Mock private OffloadHardwareInterface mHardware;
|
||||
@Mock private ApplicationInfo mApplicationInfo;
|
||||
@Mock private Context mContext;
|
||||
@Mock private INetworkManagementService mNMService;
|
||||
private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
|
||||
ArgumentCaptor.forClass(ArrayList.class);
|
||||
private final ArgumentCaptor<ITetheringStatsProvider.Stub> mTetherStatsProviderCaptor =
|
||||
ArgumentCaptor.forClass(ITetheringStatsProvider.Stub.class);
|
||||
private final ArgumentCaptor<OffloadHardwareInterface.ControlCallback> mControlCallbackCaptor =
|
||||
ArgumentCaptor.forClass(OffloadHardwareInterface.ControlCallback.class);
|
||||
private MockContentResolver mContentResolver;
|
||||
|
||||
@Before public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
|
||||
when(mContext.getPackageName()).thenReturn("OffloadControllerTest");
|
||||
mContentResolver = new MockContentResolver(mContext);
|
||||
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
|
||||
when(mContext.getContentResolver()).thenReturn(mContentResolver);
|
||||
FakeSettingsProvider.clearSettingsProvider();
|
||||
}
|
||||
|
||||
@After public void tearDown() throws Exception {
|
||||
FakeSettingsProvider.clearSettingsProvider();
|
||||
}
|
||||
|
||||
private void setupFunctioningHardwareInterface() {
|
||||
when(mHardware.initOffloadConfig()).thenReturn(true);
|
||||
when(mHardware.initOffloadControl(mControlCallbackCaptor.capture()))
|
||||
.thenReturn(true);
|
||||
when(mHardware.setUpstreamParameters(anyString(), any(), any(), any())).thenReturn(true);
|
||||
when(mHardware.getForwardedStats(any())).thenReturn(new ForwardedStats());
|
||||
when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true);
|
||||
}
|
||||
|
||||
private void enableOffload() {
|
||||
Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0);
|
||||
}
|
||||
|
||||
private void waitForIdle() {
|
||||
HandlerUtilsKt.waitForIdle(new Handler(Looper.getMainLooper()), WAIT_FOR_IDLE_TIMEOUT);
|
||||
}
|
||||
|
||||
private OffloadController makeOffloadController() throws Exception {
|
||||
OffloadController offload = new OffloadController(new Handler(Looper.getMainLooper()),
|
||||
mHardware, mContentResolver, mNMService, new SharedLog("test"));
|
||||
verify(mNMService).registerTetheringStatsProvider(
|
||||
mTetherStatsProviderCaptor.capture(), anyString());
|
||||
return offload;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoSettingsValueDefaultDisabledDoesNotStart() throws Exception {
|
||||
setupFunctioningHardwareInterface();
|
||||
when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(1);
|
||||
assertThrows(SettingNotFoundException.class, () ->
|
||||
Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED));
|
||||
|
||||
final OffloadController offload = makeOffloadController();
|
||||
offload.start();
|
||||
|
||||
final InOrder inOrder = inOrder(mHardware);
|
||||
inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled();
|
||||
inOrder.verify(mHardware, never()).initOffloadConfig();
|
||||
inOrder.verify(mHardware, never()).initOffloadControl(
|
||||
any(OffloadHardwareInterface.ControlCallback.class));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoSettingsValueDefaultEnabledDoesStart() throws Exception {
|
||||
setupFunctioningHardwareInterface();
|
||||
when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(0);
|
||||
assertThrows(SettingNotFoundException.class, () ->
|
||||
Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED));
|
||||
|
||||
final OffloadController offload = makeOffloadController();
|
||||
offload.start();
|
||||
|
||||
final InOrder inOrder = inOrder(mHardware);
|
||||
inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled();
|
||||
inOrder.verify(mHardware, times(1)).initOffloadConfig();
|
||||
inOrder.verify(mHardware, times(1)).initOffloadControl(
|
||||
any(OffloadHardwareInterface.ControlCallback.class));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSettingsAllowsStart() throws Exception {
|
||||
setupFunctioningHardwareInterface();
|
||||
Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0);
|
||||
|
||||
final OffloadController offload = makeOffloadController();
|
||||
offload.start();
|
||||
|
||||
final InOrder inOrder = inOrder(mHardware);
|
||||
inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled();
|
||||
inOrder.verify(mHardware, times(1)).initOffloadConfig();
|
||||
inOrder.verify(mHardware, times(1)).initOffloadControl(
|
||||
any(OffloadHardwareInterface.ControlCallback.class));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSettingsDisablesStart() throws Exception {
|
||||
setupFunctioningHardwareInterface();
|
||||
Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 1);
|
||||
|
||||
final OffloadController offload = makeOffloadController();
|
||||
offload.start();
|
||||
|
||||
final InOrder inOrder = inOrder(mHardware);
|
||||
inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled();
|
||||
inOrder.verify(mHardware, never()).initOffloadConfig();
|
||||
inOrder.verify(mHardware, never()).initOffloadControl(anyObject());
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetUpstreamLinkPropertiesWorking() throws Exception {
|
||||
setupFunctioningHardwareInterface();
|
||||
enableOffload();
|
||||
|
||||
final OffloadController offload = makeOffloadController();
|
||||
offload.start();
|
||||
|
||||
final InOrder inOrder = inOrder(mHardware);
|
||||
inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled();
|
||||
inOrder.verify(mHardware, times(1)).initOffloadConfig();
|
||||
inOrder.verify(mHardware, times(1)).initOffloadControl(
|
||||
any(OffloadHardwareInterface.ControlCallback.class));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
// In reality, the UpstreamNetworkMonitor would have passed down to us
|
||||
// a covering set of local prefixes representing a minimum essential
|
||||
// set plus all the prefixes on networks with network agents.
|
||||
//
|
||||
// We simulate that there, and then add upstream elements one by one
|
||||
// and watch what happens.
|
||||
final Set<IpPrefix> minimumLocalPrefixes = new HashSet<>();
|
||||
for (String s : new String[]{
|
||||
"127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64"}) {
|
||||
minimumLocalPrefixes.add(new IpPrefix(s));
|
||||
}
|
||||
offload.setLocalPrefixes(minimumLocalPrefixes);
|
||||
inOrder.verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture());
|
||||
ArrayList<String> localPrefixes = mStringArrayCaptor.getValue();
|
||||
assertEquals(4, localPrefixes.size());
|
||||
assertContainsAll(localPrefixes,
|
||||
"127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64");
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
offload.setUpstreamLinkProperties(null);
|
||||
// No change in local addresses means no call to setLocalPrefixes().
|
||||
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
|
||||
// This LinkProperties value does not differ from the default upstream.
|
||||
// There should be no extraneous call to setUpstreamParameters().
|
||||
inOrder.verify(mHardware, never()).setUpstreamParameters(
|
||||
anyObject(), anyObject(), anyObject(), anyObject());
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
final LinkProperties lp = new LinkProperties();
|
||||
|
||||
final String testIfName = "rmnet_data17";
|
||||
lp.setInterfaceName(testIfName);
|
||||
offload.setUpstreamLinkProperties(lp);
|
||||
// No change in local addresses means no call to setLocalPrefixes().
|
||||
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
|
||||
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
|
||||
eq(testIfName), eq(null), eq(null), eq(null));
|
||||
inOrder.verify(mHardware, times(1)).setDataLimit(eq(testIfName), eq(Long.MAX_VALUE));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
final String ipv4Addr = "192.0.2.5";
|
||||
final String linkAddr = ipv4Addr + "/24";
|
||||
lp.addLinkAddress(new LinkAddress(linkAddr));
|
||||
lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24")));
|
||||
offload.setUpstreamLinkProperties(lp);
|
||||
// IPv4 prefixes and addresses on the upstream are simply left as whole
|
||||
// prefixes (already passed in from UpstreamNetworkMonitor code). If a
|
||||
// tethering client sends traffic to the IPv4 default router or other
|
||||
// clients on the upstream this will not be hardware-forwarded, and that
|
||||
// should be fine for now. Ergo: no change in local addresses, no call
|
||||
// to setLocalPrefixes().
|
||||
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
|
||||
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
|
||||
eq(testIfName), eq(ipv4Addr), eq(null), eq(null));
|
||||
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
|
||||
inOrder.verify(mHardware, times(1)).setDataLimit(eq(testIfName), eq(Long.MAX_VALUE));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
final String ipv4Gateway = "192.0.2.1";
|
||||
lp.addRoute(new RouteInfo(InetAddress.getByName(ipv4Gateway)));
|
||||
offload.setUpstreamLinkProperties(lp);
|
||||
// No change in local addresses means no call to setLocalPrefixes().
|
||||
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
|
||||
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
|
||||
eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), eq(null));
|
||||
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
|
||||
inOrder.verify(mHardware, times(1)).setDataLimit(eq(testIfName), eq(Long.MAX_VALUE));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
final String ipv6Gw1 = "fe80::cafe";
|
||||
lp.addRoute(new RouteInfo(InetAddress.getByName(ipv6Gw1)));
|
||||
offload.setUpstreamLinkProperties(lp);
|
||||
// No change in local addresses means no call to setLocalPrefixes().
|
||||
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
|
||||
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
|
||||
eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
|
||||
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
|
||||
ArrayList<String> v6gws = mStringArrayCaptor.getValue();
|
||||
assertEquals(1, v6gws.size());
|
||||
assertTrue(v6gws.contains(ipv6Gw1));
|
||||
inOrder.verify(mHardware, times(1)).setDataLimit(eq(testIfName), eq(Long.MAX_VALUE));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
final String ipv6Gw2 = "fe80::d00d";
|
||||
lp.addRoute(new RouteInfo(InetAddress.getByName(ipv6Gw2)));
|
||||
offload.setUpstreamLinkProperties(lp);
|
||||
// No change in local addresses means no call to setLocalPrefixes().
|
||||
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
|
||||
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
|
||||
eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
|
||||
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
|
||||
v6gws = mStringArrayCaptor.getValue();
|
||||
assertEquals(2, v6gws.size());
|
||||
assertTrue(v6gws.contains(ipv6Gw1));
|
||||
assertTrue(v6gws.contains(ipv6Gw2));
|
||||
inOrder.verify(mHardware, times(1)).setDataLimit(eq(testIfName), eq(Long.MAX_VALUE));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
final LinkProperties stacked = new LinkProperties();
|
||||
stacked.setInterfaceName("stacked");
|
||||
stacked.addLinkAddress(new LinkAddress("192.0.2.129/25"));
|
||||
stacked.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254")));
|
||||
stacked.addRoute(new RouteInfo(InetAddress.getByName("fe80::bad:f00")));
|
||||
assertTrue(lp.addStackedLink(stacked));
|
||||
offload.setUpstreamLinkProperties(lp);
|
||||
// No change in local addresses means no call to setLocalPrefixes().
|
||||
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
|
||||
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
|
||||
eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
|
||||
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
|
||||
v6gws = mStringArrayCaptor.getValue();
|
||||
assertEquals(2, v6gws.size());
|
||||
assertTrue(v6gws.contains(ipv6Gw1));
|
||||
assertTrue(v6gws.contains(ipv6Gw2));
|
||||
inOrder.verify(mHardware, times(1)).setDataLimit(eq(testIfName), eq(Long.MAX_VALUE));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
// Add in some IPv6 upstream info. When there is a tethered downstream
|
||||
// making use of the IPv6 prefix we would expect to see the /64 route
|
||||
// removed from "local prefixes" and /128s added for the upstream IPv6
|
||||
// addresses. This is not yet implemented, and for now we simply
|
||||
// expect to see these /128s.
|
||||
lp.addRoute(new RouteInfo(new IpPrefix("2001:db8::/64")));
|
||||
// "2001:db8::/64" plus "assigned" ASCII in hex
|
||||
lp.addLinkAddress(new LinkAddress("2001:db8::6173:7369:676e:6564/64"));
|
||||
// "2001:db8::/64" plus "random" ASCII in hex
|
||||
lp.addLinkAddress(new LinkAddress("2001:db8::7261:6e64:6f6d/64"));
|
||||
offload.setUpstreamLinkProperties(lp);
|
||||
inOrder.verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture());
|
||||
localPrefixes = mStringArrayCaptor.getValue();
|
||||
assertEquals(6, localPrefixes.size());
|
||||
assertContainsAll(localPrefixes,
|
||||
"127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64",
|
||||
"2001:db8::6173:7369:676e:6564/128", "2001:db8::7261:6e64:6f6d/128");
|
||||
// The relevant parts of the LinkProperties have not changed, but at the
|
||||
// moment we do not de-dup upstream LinkProperties this carefully.
|
||||
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
|
||||
eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
|
||||
v6gws = mStringArrayCaptor.getValue();
|
||||
assertEquals(2, v6gws.size());
|
||||
assertTrue(v6gws.contains(ipv6Gw1));
|
||||
assertTrue(v6gws.contains(ipv6Gw2));
|
||||
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
|
||||
inOrder.verify(mHardware, times(1)).setDataLimit(eq(testIfName), eq(Long.MAX_VALUE));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
// Completely identical LinkProperties updates are de-duped.
|
||||
offload.setUpstreamLinkProperties(lp);
|
||||
// This LinkProperties value does not differ from the default upstream.
|
||||
// There should be no extraneous call to setUpstreamParameters().
|
||||
inOrder.verify(mHardware, never()).setUpstreamParameters(
|
||||
anyObject(), anyObject(), anyObject(), anyObject());
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
private void assertNetworkStats(String iface, ForwardedStats stats, NetworkStats.Entry entry) {
|
||||
assertEquals(iface, entry.iface);
|
||||
assertEquals(stats.rxBytes, entry.rxBytes);
|
||||
assertEquals(stats.txBytes, entry.txBytes);
|
||||
assertEquals(SET_DEFAULT, entry.set);
|
||||
assertEquals(TAG_NONE, entry.tag);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetForwardedStats() throws Exception {
|
||||
setupFunctioningHardwareInterface();
|
||||
enableOffload();
|
||||
|
||||
final OffloadController offload = makeOffloadController();
|
||||
offload.start();
|
||||
|
||||
final String ethernetIface = "eth1";
|
||||
final String mobileIface = "rmnet_data0";
|
||||
|
||||
ForwardedStats ethernetStats = new ForwardedStats();
|
||||
ethernetStats.rxBytes = 12345;
|
||||
ethernetStats.txBytes = 54321;
|
||||
|
||||
ForwardedStats mobileStats = new ForwardedStats();
|
||||
mobileStats.rxBytes = 999;
|
||||
mobileStats.txBytes = 99999;
|
||||
|
||||
when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
|
||||
when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(mobileStats);
|
||||
|
||||
InOrder inOrder = inOrder(mHardware);
|
||||
|
||||
final LinkProperties lp = new LinkProperties();
|
||||
lp.setInterfaceName(ethernetIface);
|
||||
offload.setUpstreamLinkProperties(lp);
|
||||
// Previous upstream was null, so no stats are fetched.
|
||||
inOrder.verify(mHardware, never()).getForwardedStats(any());
|
||||
|
||||
lp.setInterfaceName(mobileIface);
|
||||
offload.setUpstreamLinkProperties(lp);
|
||||
// Expect that we fetch stats from the previous upstream.
|
||||
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(ethernetIface));
|
||||
|
||||
lp.setInterfaceName(ethernetIface);
|
||||
offload.setUpstreamLinkProperties(lp);
|
||||
// Expect that we fetch stats from the previous upstream.
|
||||
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(mobileIface));
|
||||
|
||||
ethernetStats = new ForwardedStats();
|
||||
ethernetStats.rxBytes = 100000;
|
||||
ethernetStats.txBytes = 100000;
|
||||
when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
|
||||
offload.setUpstreamLinkProperties(null);
|
||||
// Expect that we first clear the HAL's upstream parameters.
|
||||
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
|
||||
eq(""), eq("0.0.0.0"), eq("0.0.0.0"), eq(null));
|
||||
// Expect that we fetch stats from the previous upstream.
|
||||
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(ethernetIface));
|
||||
|
||||
ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue();
|
||||
NetworkStats stats = provider.getTetherStats(STATS_PER_IFACE);
|
||||
NetworkStats perUidStats = provider.getTetherStats(STATS_PER_UID);
|
||||
waitForIdle();
|
||||
// There is no current upstream, so no stats are fetched.
|
||||
inOrder.verify(mHardware, never()).getForwardedStats(any());
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
assertEquals(2, stats.size());
|
||||
assertEquals(2, perUidStats.size());
|
||||
|
||||
NetworkStats.Entry entry = null;
|
||||
for (int i = 0; i < stats.size(); i++) {
|
||||
assertEquals(UID_ALL, stats.getValues(i, entry).uid);
|
||||
assertEquals(UID_TETHERING, perUidStats.getValues(i, entry).uid);
|
||||
}
|
||||
|
||||
int ethernetPosition = ethernetIface.equals(stats.getValues(0, entry).iface) ? 0 : 1;
|
||||
int mobilePosition = 1 - ethernetPosition;
|
||||
|
||||
entry = stats.getValues(mobilePosition, entry);
|
||||
assertNetworkStats(mobileIface, mobileStats, entry);
|
||||
entry = perUidStats.getValues(mobilePosition, entry);
|
||||
assertNetworkStats(mobileIface, mobileStats, entry);
|
||||
|
||||
ethernetStats.rxBytes = 12345 + 100000;
|
||||
ethernetStats.txBytes = 54321 + 100000;
|
||||
entry = stats.getValues(ethernetPosition, entry);
|
||||
assertNetworkStats(ethernetIface, ethernetStats, entry);
|
||||
entry = perUidStats.getValues(ethernetPosition, entry);
|
||||
assertNetworkStats(ethernetIface, ethernetStats, entry);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetInterfaceQuota() throws Exception {
|
||||
setupFunctioningHardwareInterface();
|
||||
enableOffload();
|
||||
|
||||
final OffloadController offload = makeOffloadController();
|
||||
offload.start();
|
||||
|
||||
final String ethernetIface = "eth1";
|
||||
final String mobileIface = "rmnet_data0";
|
||||
final long ethernetLimit = 12345;
|
||||
final long mobileLimit = 12345678;
|
||||
|
||||
final LinkProperties lp = new LinkProperties();
|
||||
lp.setInterfaceName(ethernetIface);
|
||||
offload.setUpstreamLinkProperties(lp);
|
||||
|
||||
ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue();
|
||||
final InOrder inOrder = inOrder(mHardware);
|
||||
when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true);
|
||||
when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true);
|
||||
|
||||
// Applying an interface quota to the current upstream immediately sends it to the hardware.
|
||||
provider.setInterfaceQuota(ethernetIface, ethernetLimit);
|
||||
waitForIdle();
|
||||
inOrder.verify(mHardware).setDataLimit(ethernetIface, ethernetLimit);
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
// Applying an interface quota to another upstream does not take any immediate action.
|
||||
provider.setInterfaceQuota(mobileIface, mobileLimit);
|
||||
waitForIdle();
|
||||
inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong());
|
||||
|
||||
// Switching to that upstream causes the quota to be applied if the parameters were applied
|
||||
// correctly.
|
||||
lp.setInterfaceName(mobileIface);
|
||||
offload.setUpstreamLinkProperties(lp);
|
||||
waitForIdle();
|
||||
inOrder.verify(mHardware).setDataLimit(mobileIface, mobileLimit);
|
||||
|
||||
// Setting a limit of ITetheringStatsProvider.QUOTA_UNLIMITED causes the limit to be set
|
||||
// to Long.MAX_VALUE.
|
||||
provider.setInterfaceQuota(mobileIface, ITetheringStatsProvider.QUOTA_UNLIMITED);
|
||||
waitForIdle();
|
||||
inOrder.verify(mHardware).setDataLimit(mobileIface, Long.MAX_VALUE);
|
||||
|
||||
// If setting upstream parameters fails, then the data limit is not set.
|
||||
when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(false);
|
||||
lp.setInterfaceName(ethernetIface);
|
||||
offload.setUpstreamLinkProperties(lp);
|
||||
provider.setInterfaceQuota(mobileIface, mobileLimit);
|
||||
waitForIdle();
|
||||
inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong());
|
||||
|
||||
// If setting the data limit fails while changing upstreams, offload is stopped.
|
||||
when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true);
|
||||
when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(false);
|
||||
lp.setInterfaceName(mobileIface);
|
||||
offload.setUpstreamLinkProperties(lp);
|
||||
provider.setInterfaceQuota(mobileIface, mobileLimit);
|
||||
waitForIdle();
|
||||
inOrder.verify(mHardware).getForwardedStats(ethernetIface);
|
||||
inOrder.verify(mHardware).stopOffloadControl();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataLimitCallback() throws Exception {
|
||||
setupFunctioningHardwareInterface();
|
||||
enableOffload();
|
||||
|
||||
final OffloadController offload = makeOffloadController();
|
||||
offload.start();
|
||||
|
||||
OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
|
||||
callback.onStoppedLimitReached();
|
||||
verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddRemoveDownstreams() throws Exception {
|
||||
setupFunctioningHardwareInterface();
|
||||
enableOffload();
|
||||
|
||||
final OffloadController offload = makeOffloadController();
|
||||
offload.start();
|
||||
|
||||
final InOrder inOrder = inOrder(mHardware);
|
||||
inOrder.verify(mHardware, times(1)).initOffloadConfig();
|
||||
inOrder.verify(mHardware, times(1)).initOffloadControl(
|
||||
any(OffloadHardwareInterface.ControlCallback.class));
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
// Tethering makes several calls to setLocalPrefixes() before add/remove
|
||||
// downstream calls are made. This is not tested here; only the behavior
|
||||
// of notifyDownstreamLinkProperties() and removeDownstreamInterface()
|
||||
// are tested.
|
||||
|
||||
// [1] USB tethering is started.
|
||||
final LinkProperties usbLinkProperties = new LinkProperties();
|
||||
usbLinkProperties.setInterfaceName(RNDIS0);
|
||||
usbLinkProperties.addLinkAddress(new LinkAddress("192.168.42.1/24"));
|
||||
usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(USB_PREFIX)));
|
||||
offload.notifyDownstreamLinkProperties(usbLinkProperties);
|
||||
inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, USB_PREFIX);
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
// [2] Routes for IPv6 link-local prefixes should never be added.
|
||||
usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_LINKLOCAL)));
|
||||
offload.notifyDownstreamLinkProperties(usbLinkProperties);
|
||||
inOrder.verify(mHardware, never()).addDownstreamPrefix(eq(RNDIS0), anyString());
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
// [3] Add an IPv6 prefix for good measure. Only new offload-able
|
||||
// prefixes should be passed to the HAL.
|
||||
usbLinkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64"));
|
||||
usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX)));
|
||||
offload.notifyDownstreamLinkProperties(usbLinkProperties);
|
||||
inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX);
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
// [4] Adding addresses doesn't affect notifyDownstreamLinkProperties().
|
||||
// The address is passed in by a separate setLocalPrefixes() invocation.
|
||||
usbLinkProperties.addLinkAddress(new LinkAddress("2001:db8::2/64"));
|
||||
offload.notifyDownstreamLinkProperties(usbLinkProperties);
|
||||
inOrder.verify(mHardware, never()).addDownstreamPrefix(eq(RNDIS0), anyString());
|
||||
|
||||
// [5] Differences in local routes are converted into addDownstream()
|
||||
// and removeDownstream() invocations accordingly.
|
||||
usbLinkProperties.removeRoute(new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX), null, RNDIS0));
|
||||
usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_DISCARD_PREFIX)));
|
||||
offload.notifyDownstreamLinkProperties(usbLinkProperties);
|
||||
inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX);
|
||||
inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DISCARD_PREFIX);
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
// [6] Removing a downstream interface which was never added causes no
|
||||
// interactions with the HAL.
|
||||
offload.removeDownstreamInterface(WLAN0);
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
// [7] Removing an active downstream removes all remaining prefixes.
|
||||
offload.removeDownstreamInterface(RNDIS0);
|
||||
inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, USB_PREFIX);
|
||||
inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, IPV6_DISCARD_PREFIX);
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testControlCallbackOnStoppedUnsupportedFetchesAllStats() throws Exception {
|
||||
setupFunctioningHardwareInterface();
|
||||
enableOffload();
|
||||
|
||||
final OffloadController offload = makeOffloadController();
|
||||
offload.start();
|
||||
|
||||
// Pretend to set a few different upstreams (only the interface name
|
||||
// matters for this test; we're ignoring IP and route information).
|
||||
final LinkProperties upstreamLp = new LinkProperties();
|
||||
for (String ifname : new String[]{RMNET0, WLAN0, RMNET0}) {
|
||||
upstreamLp.setInterfaceName(ifname);
|
||||
offload.setUpstreamLinkProperties(upstreamLp);
|
||||
}
|
||||
|
||||
// Clear invocation history, especially the getForwardedStats() calls
|
||||
// that happen with setUpstreamParameters().
|
||||
clearInvocations(mHardware);
|
||||
|
||||
OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
|
||||
callback.onStoppedUnsupported();
|
||||
|
||||
// Verify forwarded stats behaviour.
|
||||
verify(mHardware, times(1)).getForwardedStats(eq(RMNET0));
|
||||
verify(mHardware, times(1)).getForwardedStats(eq(WLAN0));
|
||||
verifyNoMoreInteractions(mHardware);
|
||||
verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
|
||||
verifyNoMoreInteractions(mNMService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testControlCallbackOnSupportAvailableFetchesAllStatsAndPushesAllParameters()
|
||||
throws Exception {
|
||||
setupFunctioningHardwareInterface();
|
||||
enableOffload();
|
||||
|
||||
final OffloadController offload = makeOffloadController();
|
||||
offload.start();
|
||||
|
||||
// Pretend to set a few different upstreams (only the interface name
|
||||
// matters for this test; we're ignoring IP and route information).
|
||||
final LinkProperties upstreamLp = new LinkProperties();
|
||||
for (String ifname : new String[]{RMNET0, WLAN0, RMNET0}) {
|
||||
upstreamLp.setInterfaceName(ifname);
|
||||
offload.setUpstreamLinkProperties(upstreamLp);
|
||||
}
|
||||
|
||||
// Pretend that some local prefixes and downstreams have been added
|
||||
// (and removed, for good measure).
|
||||
final Set<IpPrefix> minimumLocalPrefixes = new HashSet<>();
|
||||
for (String s : new String[]{
|
||||
"127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64"}) {
|
||||
minimumLocalPrefixes.add(new IpPrefix(s));
|
||||
}
|
||||
offload.setLocalPrefixes(minimumLocalPrefixes);
|
||||
|
||||
final LinkProperties usbLinkProperties = new LinkProperties();
|
||||
usbLinkProperties.setInterfaceName(RNDIS0);
|
||||
usbLinkProperties.addLinkAddress(new LinkAddress("192.168.42.1/24"));
|
||||
usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(USB_PREFIX)));
|
||||
offload.notifyDownstreamLinkProperties(usbLinkProperties);
|
||||
|
||||
final LinkProperties wifiLinkProperties = new LinkProperties();
|
||||
wifiLinkProperties.setInterfaceName(WLAN0);
|
||||
wifiLinkProperties.addLinkAddress(new LinkAddress("192.168.43.1/24"));
|
||||
wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix(WIFI_PREFIX)));
|
||||
wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_LINKLOCAL)));
|
||||
// Use a benchmark prefix (RFC 5180 + erratum), since the documentation
|
||||
// prefix is included in the excluded prefix list.
|
||||
wifiLinkProperties.addLinkAddress(new LinkAddress("2001:2::1/64"));
|
||||
wifiLinkProperties.addLinkAddress(new LinkAddress("2001:2::2/64"));
|
||||
wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix("2001:2::/64")));
|
||||
offload.notifyDownstreamLinkProperties(wifiLinkProperties);
|
||||
|
||||
offload.removeDownstreamInterface(RNDIS0);
|
||||
|
||||
// Clear invocation history, especially the getForwardedStats() calls
|
||||
// that happen with setUpstreamParameters().
|
||||
clearInvocations(mHardware);
|
||||
|
||||
OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
|
||||
callback.onSupportAvailable();
|
||||
|
||||
// Verify forwarded stats behaviour.
|
||||
verify(mHardware, times(1)).getForwardedStats(eq(RMNET0));
|
||||
verify(mHardware, times(1)).getForwardedStats(eq(WLAN0));
|
||||
verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
|
||||
verifyNoMoreInteractions(mNMService);
|
||||
|
||||
// TODO: verify local prefixes and downstreams are also pushed to the HAL.
|
||||
verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture());
|
||||
ArrayList<String> localPrefixes = mStringArrayCaptor.getValue();
|
||||
assertEquals(4, localPrefixes.size());
|
||||
assertContainsAll(localPrefixes,
|
||||
// TODO: The logic to find and exclude downstream IP prefixes
|
||||
// is currently in Tethering's OffloadWrapper but must be moved
|
||||
// into OffloadController proper. After this, also check for:
|
||||
// "192.168.43.1/32", "2001:2::1/128", "2001:2::2/128"
|
||||
"127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64");
|
||||
verify(mHardware, times(1)).addDownstreamPrefix(WLAN0, "192.168.43.0/24");
|
||||
verify(mHardware, times(1)).addDownstreamPrefix(WLAN0, "2001:2::/64");
|
||||
verify(mHardware, times(1)).setUpstreamParameters(eq(RMNET0), any(), any(), any());
|
||||
verify(mHardware, times(1)).setDataLimit(eq(RMNET0), anyLong());
|
||||
verifyNoMoreInteractions(mHardware);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user