Merge "Revert https://r.android.com/3477936" into main
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index f123dca..0ac97f0 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -333,6 +333,11 @@
public static final int TETHER_ERROR_UNKNOWN_REQUEST = 17;
@FlaggedApi(Flags.FLAG_TETHERING_WITH_SOFT_AP_CONFIG)
public static final int TETHER_ERROR_DUPLICATE_REQUEST = 18;
+ /**
+ * Never used outside Tethering.java.
+ * @hide
+ */
+ public static final int TETHER_ERROR_BLUETOOTH_SERVICE_PENDING = 19;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index c0192dd..b50831d 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -43,6 +43,7 @@
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
import static android.net.TetheringManager.TETHERING_WIGIG;
+import static android.net.TetheringManager.TETHER_ERROR_BLUETOOTH_SERVICE_PENDING;
import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
@@ -707,8 +708,7 @@
// If tethering is already enabled with a different request,
// disable before re-enabling.
if (unfinishedRequest != null && !unfinishedRequest.equalsIgnoreUidPackage(request)) {
- enableTetheringInternal(type, false /* disabled */,
- unfinishedRequest.getInterfaceName(), null);
+ enableTetheringInternal(false /* disabled */, unfinishedRequest, null);
mEntitlementMgr.stopProvisioningIfNeeded(type);
}
mPendingTetheringRequests.put(type, request);
@@ -719,7 +719,7 @@
mEntitlementMgr.startProvisioningIfNeeded(type,
request.getShouldShowEntitlementUi());
}
- enableTetheringInternal(type, true /* enabled */, request.getInterfaceName(), listener);
+ enableTetheringInternal(true /* enabled */, request, listener);
mTetheringMetrics.createBuilder(type, callerPkg);
});
}
@@ -766,7 +766,10 @@
void stopTetheringInternal(int type) {
mPendingTetheringRequests.remove(type);
- enableTetheringInternal(type, false /* disabled */, null, null);
+ // Using a placeholder here is ok since none of the disable APIs use the request for
+ // anything. We simply need the tethering type to know which link layer to poke for removal.
+ // TODO: Remove the placeholder here and loop through each pending/serving request.
+ enableTetheringInternal(false /* disabled */, createPlaceholderRequest(type), null);
mEntitlementMgr.stopProvisioningIfNeeded(type);
}
@@ -774,9 +777,10 @@
* Enables or disables tethering for the given type. If provisioning is required, it will
* schedule provisioning rechecks for the specified interface.
*/
- private void enableTetheringInternal(int type, boolean enable,
- String iface, final IIntResultListener listener) {
- int result = TETHER_ERROR_NO_ERROR;
+ private void enableTetheringInternal(boolean enable, @NonNull final TetheringRequest request,
+ final IIntResultListener listener) {
+ final int type = request.getTetheringType();
+ final int result;
switch (type) {
case TETHERING_WIFI:
result = setWifiTethering(enable);
@@ -785,7 +789,7 @@
result = setUsbTethering(enable);
break;
case TETHERING_BLUETOOTH:
- setBluetoothTethering(enable, listener);
+ result = setBluetoothTethering(enable, listener);
break;
case TETHERING_NCM:
result = setNcmTethering(enable);
@@ -794,17 +798,17 @@
result = setEthernetTethering(enable);
break;
case TETHERING_VIRTUAL:
- result = setVirtualMachineTethering(enable, iface);
+ result = setVirtualMachineTethering(enable, request);
break;
default:
Log.w(TAG, "Invalid tether type.");
result = TETHER_ERROR_UNKNOWN_TYPE;
}
- // The result of Bluetooth tethering will be sent by #setBluetoothTethering.
- if (type != TETHERING_BLUETOOTH) {
- sendTetherResult(listener, result, type);
- }
+ // The result of Bluetooth tethering will be sent after the pan service connects.
+ if (result == TETHER_ERROR_BLUETOOTH_SERVICE_PENDING) return;
+
+ sendTetherResult(listener, result, type);
}
private void sendTetherResult(final IIntResultListener listener, final int result,
@@ -843,13 +847,12 @@
return TETHER_ERROR_INTERNAL_ERROR;
}
- private void setBluetoothTethering(final boolean enable, final IIntResultListener listener) {
+ private int setBluetoothTethering(final boolean enable, final IIntResultListener listener) {
final BluetoothAdapter adapter = mDeps.getBluetoothAdapter();
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, TETHERING_BLUETOOTH);
- return;
+ return TETHER_ERROR_SERVICE_UNAVAIL;
}
if (mBluetoothPanListener != null && mBluetoothPanListener.isConnected()) {
@@ -857,8 +860,7 @@
// When bluetooth tethering is enabled, any time a PAN client pairs with this
// host, bluetooth will bring up a bt-pan interface and notify tethering to
// enable IP serving.
- setBluetoothTetheringSettings(mBluetoothPan, enable, listener);
- return;
+ return setBluetoothTetheringSettings(mBluetoothPan, enable);
}
if (!enable) {
@@ -870,8 +872,7 @@
TETHERING_BLUETOOTH);
}
mPendingPanRequestListeners.clear();
- sendTetherResult(listener, TETHER_ERROR_NO_ERROR, TETHERING_BLUETOOTH);
- return;
+ return TETHER_ERROR_NO_ERROR;
}
mPendingPanRequestListeners.add(listener);
@@ -883,6 +884,7 @@
mBluetoothPanListener = new PanServiceListener();
adapter.getProfileProxy(mContext, mBluetoothPanListener, BluetoothProfile.PAN);
}
+ return TETHER_ERROR_BLUETOOTH_SERVICE_PENDING;
}
private class PanServiceListener implements ServiceListener {
@@ -900,8 +902,9 @@
mIsConnected = true;
for (IIntResultListener pendingListener : mPendingPanRequestListeners) {
- setBluetoothTetheringSettings(mBluetoothPan, true /* enable */,
- pendingListener);
+ final int result = setBluetoothTetheringSettings(mBluetoothPan,
+ true /* enable */);
+ sendTetherResult(pendingListener, result, TETHERING_BLUETOOTH);
}
mPendingPanRequestListeners.clear();
});
@@ -930,8 +933,8 @@
}
}
- private void setBluetoothTetheringSettings(@NonNull final BluetoothPan bluetoothPan,
- final boolean enable, final IIntResultListener listener) {
+ private int setBluetoothTetheringSettings(@NonNull final BluetoothPan bluetoothPan,
+ final boolean enable) {
if (SdkLevel.isAtLeastT()) {
changeBluetoothTetheringSettings(bluetoothPan, enable);
} else {
@@ -940,9 +943,8 @@
// Enabling bluetooth tethering settings can silently fail. Send internal error if the
// result is not expected.
- final int result = bluetoothPan.isTetheringOn() == enable
+ return bluetoothPan.isTetheringOn() == enable
? TETHER_ERROR_NO_ERROR : TETHER_ERROR_INTERNAL_ERROR;
- sendTetherResult(listener, result, TETHERING_BLUETOOTH);
}
private void changeBluetoothTetheringSettingsPreT(@NonNull final BluetoothPan bluetoothPan,
@@ -1061,14 +1063,15 @@
}
}
- private int setVirtualMachineTethering(final boolean enable, String iface) {
+ private int setVirtualMachineTethering(final boolean enable,
+ @NonNull final TetheringRequest request) {
+ final String iface = request.getInterfaceName();
if (enable) {
if (TextUtils.isEmpty(iface)) {
mConfiguredVirtualIface = "avf_tap_fixed";
} else {
mConfiguredVirtualIface = iface;
}
- final TetheringRequest request = getOrCreatePendingTetheringRequest(TETHERING_VIRTUAL);
enableIpServing(request, mConfiguredVirtualIface);
} else if (mConfiguredVirtualIface != null) {
ensureIpServerStopped(mConfiguredVirtualIface);
@@ -1101,6 +1104,19 @@
}
/**
+ * Create a placeholder request. This is used in case we try to find a pending request but there
+ * is none (e.g. stopTethering removed a pending request), or for cases where we only have the
+ * tethering type (e.g. stopTethering(int)).
+ */
+ @NonNull
+ private TetheringRequest createPlaceholderRequest(int type) {
+ final TetheringRequest request = new TetheringRequest.Builder(type).build();
+ request.getParcel().requestType = TetheringRequest.REQUEST_TYPE_LEGACY;
+ request.getParcel().connectivityScope = CONNECTIVITY_SCOPE_GLOBAL;
+ return request;
+ }
+
+ /**
* Gets the TetheringRequest that #startTethering was called with but is waiting for the link
* layer event to indicate the interface is available to tether.
* Note: There are edge cases where the pending request is absent and we must temporarily
@@ -1116,9 +1132,7 @@
Log.w(TAG, "No pending TetheringRequest for type " + type + " found, creating a placeholder"
+ " request");
- TetheringRequest placeholder = new TetheringRequest.Builder(type).build();
- placeholder.getParcel().requestType = REQUEST_TYPE_PLACEHOLDER;
- return placeholder;
+ return createPlaceholderRequest(type);
}
private void handleLegacyTether(String iface, final IIntResultListener listener) {
@@ -2414,9 +2428,14 @@
break;
}
case EVENT_REQUEST_CHANGE_DOWNSTREAM: {
- final int tetheringType = message.arg1;
+ final int type = message.arg1;
final Boolean enabled = (Boolean) message.obj;
- enableTetheringInternal(tetheringType, enabled, null, null);
+ // Using a placeholder here is ok since we just need to the type of
+ // tethering to poke the link layer. When the link layer comes up, we won't
+ // have a pending request to use, but this matches the historical behavior.
+ // TODO: Get the TetheringRequest from IpServer and make sure to put it in
+ // the pending list too.
+ enableTetheringInternal(enabled, createPlaceholderRequest(type), null);
break;
}
default:
diff --git a/bpf/headers/include/bpf/KernelUtils.h b/bpf/headers/include/bpf/KernelUtils.h
index 68bc607..a36085a 100644
--- a/bpf/headers/include/bpf/KernelUtils.h
+++ b/bpf/headers/include/bpf/KernelUtils.h
@@ -55,12 +55,12 @@
isKernelVersion(4, 9) || // minimum for Android S & T
isKernelVersion(4, 14) || // minimum for Android U
isKernelVersion(4, 19) || // minimum for Android V
- isKernelVersion(5, 4) || // first supported in Android R, min for W
+ isKernelVersion(5, 4) || // first supported in Android R, min for 25Q2
isKernelVersion(5, 10) || // first supported in Android S
isKernelVersion(5, 15) || // first supported in Android T
isKernelVersion(6, 1) || // first supported in Android U
isKernelVersion(6, 6) || // first supported in Android V
- isKernelVersion(6, 12); // first supported in Android W
+ isKernelVersion(6, 12); // first supported in Android 25Q2
}
// Figure out the bitness of userspace.
diff --git a/bpf/loader/NetBpfLoad.cpp b/bpf/loader/NetBpfLoad.cpp
index 1e088a6..40d1281 100644
--- a/bpf/loader/NetBpfLoad.cpp
+++ b/bpf/loader/NetBpfLoad.cpp
@@ -61,6 +61,7 @@
// The following matches bpf_helpers.h, which is only for inclusion in bpf code
#define BPFLOADER_MAINLINE_VERSION 42u
+#define BPFLOADER_MAINLINE_25Q2_VERSION 47u
using android::base::EndsWith;
using android::base::GetIntProperty;
@@ -823,14 +824,14 @@
"tmp_map_" + objName + "_" + mapNames[i];
ret = bpfFdPin(fd, createLoc.c_str());
if (ret) {
- int err = errno;
+ const int err = errno;
ALOGE("create %s -> %d [%d:%s]", createLoc.c_str(), ret, err, strerror(err));
return -err;
}
ret = renameat2(AT_FDCWD, createLoc.c_str(),
AT_FDCWD, mapPinLoc.c_str(), RENAME_NOREPLACE);
if (ret) {
- int err = errno;
+ const int err = errno;
ALOGE("rename %s %s -> %d [%d:%s]", createLoc.c_str(), mapPinLoc.c_str(), ret,
err, strerror(err));
return -err;
@@ -838,32 +839,34 @@
} else {
ret = bpfFdPin(fd, mapPinLoc.c_str());
if (ret) {
- int err = errno;
+ const int err = errno;
ALOGE("pin %s -> %d [%d:%s]", mapPinLoc.c_str(), ret, err, strerror(err));
return -err;
}
}
ret = chmod(mapPinLoc.c_str(), md[i].mode);
if (ret) {
- int err = errno;
+ const int err = errno;
ALOGE("chmod(%s, 0%o) = %d [%d:%s]", mapPinLoc.c_str(), md[i].mode, ret, err,
strerror(err));
return -err;
}
ret = chown(mapPinLoc.c_str(), (uid_t)md[i].uid, (gid_t)md[i].gid);
if (ret) {
- int err = errno;
+ const int err = errno;
ALOGE("chown(%s, %u, %u) = %d [%d:%s]", mapPinLoc.c_str(), md[i].uid, md[i].gid,
ret, err, strerror(err));
return -err;
}
}
- int mapId = bpfGetFdMapId(fd);
- if (mapId == -1) {
- if (isAtLeastKernelVersion(4, 14, 0))
- ALOGE("bpfGetFdMapId failed, ret: %d [%d]", mapId, errno);
- } else {
+ if (isAtLeastKernelVersion(4, 14, 0)) {
+ int mapId = bpfGetFdMapId(fd);
+ if (mapId == -1) {
+ const int err = errno;
+ ALOGE("bpfGetFdMapId failed, errno: %d", err);
+ return -err;
+ }
ALOGI("map %s id %d", mapPinLoc.c_str(), mapId);
}
@@ -1006,7 +1009,7 @@
if (access(progPinLoc.c_str(), F_OK) == 0) {
fd.reset(retrieveProgram(progPinLoc.c_str()));
ALOGD("New bpf prog load reusing prog %s, ret: %d (%s)", progPinLoc.c_str(), fd.get(),
- (!fd.ok() ? std::strerror(errno) : "no error"));
+ !fd.ok() ? std::strerror(errno) : "ok");
reuse = true;
} else {
static char log_buf[1 << 20]; // 1 MiB logging buffer
@@ -1037,7 +1040,7 @@
ALOGD("BPF_PROG_LOAD call for %s (%s) returned '%s' fd: %d (%s)", elfPath,
cs[i].name.c_str(), log_oneline ? log_buf : "{multiline}",
- fd.get(), (!fd.ok() ? std::strerror(errno) : "ok"));
+ fd.get(), !fd.ok() ? std::strerror(errno) : "ok");
if (!fd.ok()) {
// kernel NULL terminates log_buf, so this checks for non-empty string
@@ -1066,14 +1069,14 @@
"tmp_prog_" + objName + '_' + string(name);
ret = bpfFdPin(fd, createLoc.c_str());
if (ret) {
- int err = errno;
+ const int err = errno;
ALOGE("create %s -> %d [%d:%s]", createLoc.c_str(), ret, err, strerror(err));
return -err;
}
ret = renameat2(AT_FDCWD, createLoc.c_str(),
AT_FDCWD, progPinLoc.c_str(), RENAME_NOREPLACE);
if (ret) {
- int err = errno;
+ const int err = errno;
ALOGE("rename %s %s -> %d [%d:%s]", createLoc.c_str(), progPinLoc.c_str(), ret,
err, strerror(err));
return -err;
@@ -1081,30 +1084,52 @@
} else {
ret = bpfFdPin(fd, progPinLoc.c_str());
if (ret) {
- int err = errno;
+ const int err = errno;
ALOGE("create %s -> %d [%d:%s]", progPinLoc.c_str(), ret, err, strerror(err));
return -err;
}
}
if (chmod(progPinLoc.c_str(), 0440)) {
- int err = errno;
+ const int err = errno;
ALOGE("chmod %s 0440 -> [%d:%s]", progPinLoc.c_str(), err, strerror(err));
return -err;
}
if (chown(progPinLoc.c_str(), (uid_t)cs[i].prog_def->uid,
(gid_t)cs[i].prog_def->gid)) {
- int err = errno;
+ const int err = errno;
ALOGE("chown %s %d %d -> [%d:%s]", progPinLoc.c_str(), cs[i].prog_def->uid,
cs[i].prog_def->gid, err, strerror(err));
return -err;
}
}
- int progId = bpfGetFdProgId(fd);
- if (progId == -1) {
- ALOGE("bpfGetFdProgId failed, ret: %d [%d]", progId, errno);
- } else {
- ALOGI("prog %s id %d", progPinLoc.c_str(), progId);
+ if (isAtLeastKernelVersion(4, 14, 0)) {
+ int progId = bpfGetFdProgId(fd);
+ if (progId == -1) {
+ const int err = errno;
+ ALOGE("bpfGetFdProgId failed, errno: %d", err);
+ return -err;
+ }
+
+ int jitLen = bpfGetFdJitProgLen(fd);
+ if (jitLen == -1) {
+ const int err = errno;
+ ALOGE("bpfGetFdJitProgLen failed, ret: %d", err);
+ return -err;
+ }
+
+ int xlatLen = bpfGetFdXlatProgLen(fd);
+ if (xlatLen == -1) {
+ const int err = errno;
+ ALOGE("bpfGetFdXlatProgLen failed, ret: %d", err);
+ return -err;
+ }
+ ALOGI("prog %s id %d len jit:%d xlat:%d", progPinLoc.c_str(), progId, jitLen, xlatLen);
+
+ if (!jitLen && bpfloader_ver >= BPFLOADER_MAINLINE_25Q2_VERSION) {
+ ALOGE("Kernel eBPF JIT failure for %s", progPinLoc.c_str());
+ return -ENOTSUP;
+ }
}
}
diff --git a/bpf/syscall_wrappers/include/BpfSyscallWrappers.h b/bpf/syscall_wrappers/include/BpfSyscallWrappers.h
index a31445a..1d72b77 100644
--- a/bpf/syscall_wrappers/include/BpfSyscallWrappers.h
+++ b/bpf/syscall_wrappers/include/BpfSyscallWrappers.h
@@ -269,7 +269,7 @@
return info.FIELD; \
}
-// All 7 of these fields are already present in Linux v4.14 (even ACK 4.14-P)
+// All 9 of these fields are already present in Linux v4.14 (even ACK 4.14-P)
// while BPF_OBJ_GET_INFO_BY_FD is not implemented at all in v4.9 (even ACK 4.9-Q)
DEFINE_BPF_GET_FD(map, MapType, type) // int bpfGetFdMapType(const borrowed_fd& map_fd)
DEFINE_BPF_GET_FD(map, MapId, id) // int bpfGetFdMapId(const borrowed_fd& map_fd)
@@ -278,6 +278,8 @@
DEFINE_BPF_GET_FD(map, MaxEntries, max_entries) // int bpfGetFdMaxEntries(const borrowed_fd& map_fd)
DEFINE_BPF_GET_FD(map, MapFlags, map_flags) // int bpfGetFdMapFlags(const borrowed_fd& map_fd)
DEFINE_BPF_GET_FD(prog, ProgId, id) // int bpfGetFdProgId(const borrowed_fd& prog_fd)
+DEFINE_BPF_GET_FD(prog, JitProgLen, jited_prog_len) // int bpfGetFdJitProgLen(...)
+DEFINE_BPF_GET_FD(prog, XlatProgLen, xlated_prog_len) // int bpfGetFdXlatProgLen(...)
#undef DEFINE_BPF_GET_FD
diff --git a/common/flags.aconfig b/common/flags.aconfig
index 5f279fa..51b4fc0 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -171,6 +171,6 @@
is_exported: true
namespace: "android_core_networking"
description: "Flag for controlling access to the local network behind a new runtime permission. Requires ConnectivityCompatChanges.RESTRICT_LOCAL_NETWORK to enable feature."
- bug: "365139289"
+ bug: "388774939"
is_fixed_read_only: true
}
diff --git a/networksecurity/service/src/com/android/server/net/ct/DataStore.java b/networksecurity/service/src/com/android/server/net/ct/DataStore.java
index 8180316..1f99efa 100644
--- a/networksecurity/service/src/com/android/server/net/ct/DataStore.java
+++ b/networksecurity/service/src/com/android/server/net/ct/DataStore.java
@@ -44,8 +44,9 @@
}
try (InputStream in = new FileInputStream(mPropertyFile)) {
load(in);
- } catch (IOException e) {
+ } catch (IOException | IllegalArgumentException e) {
Log.e(TAG, "Error loading property store", e);
+ delete();
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
index 54f7ca3..c3306bd 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -1007,6 +1007,19 @@
});
}
+ private List<String> getOffloadSubtype(@NonNull NsdServiceInfo nsdServiceInfo) {
+ // Workaround: Google Cast doesn't announce subtypes per DNS-SD/mDNS spec.
+ // Thus, subtypes aren't offloaded; only "_googlecast._tcp" is.
+ // Subtype responses occur when hardware offload is off.
+ // This solution works because Google Cast doesn't follow the intended usage of subtypes in
+ // the spec, as it always discovers for both the subtype+base type, and only uses the mDNS
+ // subtype as an optimization.
+ if (nsdServiceInfo.getServiceType().equals("_googlecast._tcp")) {
+ return new ArrayList<>();
+ }
+ return new ArrayList<>(nsdServiceInfo.getSubtypes());
+ }
+
private OffloadServiceInfoWrapper createOffloadService(int serviceId,
@NonNull Registration registration, byte[] rawOffloadPacket) {
final NsdServiceInfo nsdServiceInfo = registration.getServiceInfo();
@@ -1017,7 +1030,7 @@
final OffloadServiceInfo offloadServiceInfo = new OffloadServiceInfo(
new OffloadServiceInfo.Key(nsdServiceInfo.getServiceName(),
nsdServiceInfo.getServiceType()),
- new ArrayList<>(nsdServiceInfo.getSubtypes()),
+ getOffloadSubtype(nsdServiceInfo),
String.join(".", mDeviceHostName),
rawOffloadPacket,
priority,
diff --git a/service/ServiceConnectivityResources/res/raw/ct_public_keys.pem b/service/ServiceConnectivityResources/res/raw/ct_public_keys.pem
new file mode 100644
index 0000000..80dccbe
--- /dev/null
+++ b/service/ServiceConnectivityResources/res/raw/ct_public_keys.pem
@@ -0,0 +1,42 @@
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnmb1lacOnP5H1bwb06mG
+fEUeC9PZRwNQskSs9KaWrpfrSkLKuHXkVCbgeagbUR/Sh1OeIhyJRSS0PLCO0JjC
+UpGhYMrIGRgEET4IrP9f8aMFqxxxBUEanI+OxAhIJlP9tiWfGdKAASYcxg/DyXXz
+bqSXBEFJqBLqmnfHcLB/NhO1ejV6AiU1NMrT+iWSrJG8b6mq/LlAqWvidK8oPBsW
+87M4pPLpUoA54ultjx2wEzJ9dBy6jtnKZ/dz4DkDhYug5izRDcYtEfzQBoum0etV
+s4EoogW1AMeqW5G+L4HjPNgp3gNGZ/2RaBy7gp8Br+byYu2wHwdQIBQjS310yaKc
+nuNFOd+Q0DrzvHKB7yYzzdwo+hNocPpkvOzSw74jd09kDZQ+S2peCJ5NPU7VKT6/
+tkvc3tYA9pAu4/T+BGqRft4FjgeNANfSIX/WhWDzzVWymTUGFUvt+D0fF3Cw+XSa
+b8uTgRZ1Ma/FvSGgXHVoG9E4QAFFG4I9mmRqsnzA+8fqSNAfieL5OWecq4PU+pMa
+uNVJ9hbvmW2yXuMgEg6K9kFLdxggRn+OcxowgXJJh79L0r7RN1d8kuHelDhOzcte
+dUTtLNOb/1PA0d/I2IVJwc9xSQZXurqqT/Z+c01B3/R0BgGDkIT/yZ5iHPoZFYPD
+U8UdQzUK0KXgGkc8P5pJW8kCAwEAAQ==
+-----END PUBLIC KEY-----
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAn1ssK1m56VVurVJ/fNKe
++aXDnytBy/hWY48ZT+ZC0S29llfjkaCBlmkngoI2hgwz9BI6pHwUINS15pT1sznw
+kHoEaNKr8sANHTQ0PYlDuk5iQSjnaGz7XmqbZ3c92BQrmLG/kwX2c17YNJI2qCk3
+MaBJBeS5YErR3xcucT9M7qtNWWIT9O0sLV1lDUZVCYedWBSnNz1/mAiLhttWmU5u
+GKl/5LmjWP/piNjh8whx0abJUGeGS2HH0JAOb0pjBV6UQvj3tO+gTiNDhdrE4CKh
+Qn8SKNjW/BY320f0A5t581Q0++cQUAisRgBQos0Pkvg5vb6wgII+pJq0SnZoYFfH
+oycuR4q3eOCgJmpEAPC0MhNpIDUQS6p3QabD9ID+21ymiQa/Zf8Mv2xMM6ZItKxX
+77vSKvBbimTGmB2IU+Zi484PKI5QwxBUCHVSmNpvHyXyjhBmpqik9Op26QYYT10b
+ADnJY1L/Q+i44nI4pfwgIncqAWuLnxg/XggJDWj48Un9SMNoyN7gzQX75M7rh7/t
+F6QtKvJreP0pP2UoVSgZKjXnL9tqeZbOdZU1kBHi1HOhlUKTfq5dn2fVUeYkE769
+clFF7Y1FiI259IPhTKiOIfARJ4BL4Sn8D9c9vpxDYPFl5bCJbspmFpwfzTMDnGVS
+/IlY6Putpv2/lD1B7aQGt1sCAwEAAQ==
+-----END PUBLIC KEY-----
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwMPNecDhLDamK9Rs8W5V
+c0LWZPbk9FNP+budBoFMX46dWyJ75O5e8xY9UhJSBFl8+Itm/rWO/h2zZ6qE3Cq8
+1LYbOg+x+rLnYOcAvD2O7EU73A3RD/vqoUDDVK3cwKMq3ry7CYu+NJW7TRIKV7Ct
+BMCBrvpmC1WlZ/jxSV0Soapza4/H+UV0hHYh/Wn3EWObGWYdI3yxwZ81AyU1QCR/
+oaO0PQPXqvo3gPBINnK3Qr0aLtYc4YCfTXe6i4g3DeAlkpqNLZtC2hyqiRB4Dg47
+zDzYECGofRAu8w9d8uI+eccacXfvI9zEcL7FAl5AzBKmMFOfBNTr08V7+aROWfGO
+7imWsj2MQ6RG17zqJak5QX/1bqDxwhG0KolB86mPZu0WeKz1B3iP5qAUlDNBHLDV
+pQIez0mrMsVsimVguuLYHMpIgijphA9WhijCJW2x7c6aocB6IpnMIV1sqnUQTwLG
+t32AMrckxqFmaKGj/8I9M+Xj+Cy4fIa5YSOdb/tlaYZSfjH5ch41xucQ2HWFyZ/9
+hkTFodvF5ajCQ5maHeIjDkS/Bc/s9CB+/fbSkstDsPMRp/ExyQcEYjKTG5o9Ewyo
++KGGXS2dSS10Ibl0Zx/S/0ZuZx8ZAxMOIIPpugdkWqHU9thh71dR8zM4KMkEfB8C
+sWLGB1yMuztn9nRUcpiEZTECAwEAAQ==
+-----END PUBLIC KEY-----
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index c743573..ebb8de1 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -464,7 +464,7 @@
ToDo : Remove this method when SdkLevel.isAtLeastB() is fixed, aosp is at sdk level 36 or use
NetworkStackUtils.isAtLeast25Q2 when it is moved to a static lib.
*/
- private static boolean isAtLeast25Q2() {
+ public static boolean isAtLeast25Q2() {
return SdkLevel.isAtLeastB() || (SdkLevel.isAtLeastV()
&& "Baklava".equals(Build.VERSION.CODENAME));
}
@@ -911,7 +911,7 @@
+ "(" + iface + ")");
return;
}
- LocalNetAccessKey localNetAccessKey = new LocalNetAccessKey(lpmBitlen, ifIndex,
+ final LocalNetAccessKey localNetAccessKey = new LocalNetAccessKey(lpmBitlen, ifIndex,
address, protocol, remotePort);
try {
@@ -923,13 +923,43 @@
}
/**
- * False if the configuration is disallowed.
- *
+ * Remove configuration to local_net_access trie map.
+ * @param lpmBitlen prefix length that will be used for longest matching
+ * @param iface interface name
+ * @param address remote address. ipv4 addresses would be mapped to v6
+ * @param protocol required for longest match in special cases
+ * @param remotePort src/dst port for ingress/egress
+ */
+ @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
+ public void removeLocalNetAccess(final int lpmBitlen, final String iface,
+ final InetAddress address, final int protocol, final int remotePort) {
+ throwIfPre25Q2("removeLocalNetAccess is not available on pre-B devices");
+ final int ifIndex = mDeps.getIfIndex(iface);
+ if (ifIndex == 0) {
+ Log.e(TAG, "Failed to get if index, skip removeLocalNetAccess for " + address
+ + "(" + iface + ")");
+ return;
+ }
+ final LocalNetAccessKey localNetAccessKey = new LocalNetAccessKey(lpmBitlen, ifIndex,
+ address, protocol, remotePort);
+
+ try {
+ sLocalNetAccessMap.deleteEntry(localNetAccessKey);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to remove local network access for localNetAccessKey : "
+ + localNetAccessKey);
+ }
+ }
+
+ /**
+ * Fetches value available in local_net_access bpf map for provided configuration
* @param lpmBitlen prefix length that will be used for longest matching
* @param iface interface name
* @param address remote address. ipv4 addresses would be mapped to v6
* @param protocol required for longest match in special cases
* @param remotePort src/dst port for ingress/egress
+ * @return false if the configuration is disallowed, true if the configuration is absent i.e. it
+ * is not local network or if configuration is allowed like local dns servers.
*/
@RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
public boolean getLocalNetAccess(final int lpmBitlen, final String iface,
@@ -941,10 +971,10 @@
+ address + "(" + iface + ")");
return true;
}
- LocalNetAccessKey localNetAccessKey = new LocalNetAccessKey(lpmBitlen, ifIndex,
+ final LocalNetAccessKey localNetAccessKey = new LocalNetAccessKey(lpmBitlen, ifIndex,
address, protocol, remotePort);
try {
- Bool value = sLocalNetAccessMap.getValue(localNetAccessKey);
+ final Bool value = sLocalNetAccessMap.getValue(localNetAccessKey);
return value == null ? true : value.val;
} catch (ErrnoException e) {
Log.e(TAG, "Failed to find local network access configuration for "
@@ -972,10 +1002,10 @@
* @param uid application uid that needs check if local network calls are blocked.
*/
@RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
- public boolean getUidValueFromLocalNetBlockMap(final int uid) {
- throwIfPre25Q2("getUidValueFromLocalNetBlockMap is not available on pre-B devices");
+ public boolean isUidBlockedFromUsingLocalNetwork(final int uid) {
+ throwIfPre25Q2("isUidBlockedFromUsingLocalNetwork is not available on pre-B devices");
try {
- Bool value = sLocalNetBlockedUidMap.getValue(new U32(uid));
+ final Bool value = sLocalNetBlockedUidMap.getValue(new U32(uid));
return value == null ? false : value.val;
} catch (ErrnoException e) {
Log.e(TAG, "Failed to find uid(" + uid
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 18801f0..5b415c8 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -109,8 +109,8 @@
import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
-import static android.net.NetworkCapabilities.RES_ID_UNSET;
import static android.net.NetworkCapabilities.RES_ID_MATCH_ALL_RESERVATIONS;
+import static android.net.NetworkCapabilities.RES_ID_UNSET;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
@@ -146,6 +146,8 @@
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP6_RECVMSG;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP6_SENDMSG;
import static com.android.net.module.util.NetworkMonitorUtils.isPrivateDnsValidationRequired;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_LOCAL_PREFIXES;
+import static com.android.net.module.util.NetworkStackConstants.MULTICAST_AND_BROADCAST_PREFIXES;
import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;
import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission;
import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr;
@@ -214,6 +216,7 @@
import android.net.InetAddresses;
import android.net.IpMemoryStore;
import android.net.IpPrefix;
+import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.LocalNetworkConfig;
import android.net.LocalNetworkInfo;
@@ -5683,6 +5686,7 @@
// destroyed pending replacement they will be sent when it is disconnected.
maybeDisableForwardRulesForDisconnectingNai(nai, false /* sendCallbacks */);
updateIngressToVpnAddressFiltering(null, nai.linkProperties, nai);
+ updateLocalNetworkAddresses(null, nai.linkProperties);
try {
mNetd.networkDestroy(nai.network.getNetId());
} catch (RemoteException | ServiceSpecificException e) {
@@ -9583,6 +9587,8 @@
updateIngressToVpnAddressFiltering(newLp, oldLp, networkAgent);
+ updateLocalNetworkAddresses(newLp, oldLp);
+
updateMtu(newLp, oldLp);
// TODO - figure out what to do for clat
// for (LinkProperties lp : newLp.getStackedLinks()) {
@@ -9762,6 +9768,200 @@
}
/**
+ * Update Local Network Addresses to LocalNetAccess BPF map.
+ * @param newLp new link properties
+ * @param oldLp old link properties
+ */
+ private void updateLocalNetworkAddresses(@Nullable final LinkProperties newLp,
+ @NonNull final LinkProperties oldLp) {
+
+ // The maps are available only after 25Q2 release
+ if (!BpfNetMaps.isAtLeast25Q2()) {
+ return;
+ }
+
+ final CompareResult<String> interfaceDiff = new CompareResult<>(
+ oldLp != null ? oldLp.getAllInterfaceNames() : null,
+ newLp != null ? newLp.getAllInterfaceNames() : null);
+
+ for (final String iface : interfaceDiff.added) {
+ addLocalAddressesToBpfMap(iface, MULTICAST_AND_BROADCAST_PREFIXES, newLp);
+ }
+ for (final String iface : interfaceDiff.removed) {
+ removeLocalAddressesFromBpfMap(iface, MULTICAST_AND_BROADCAST_PREFIXES, oldLp);
+ }
+
+ final CompareResult<LinkAddress> linkAddressDiff = new CompareResult<>(
+ oldLp != null ? oldLp.getLinkAddresses() : null,
+ newLp != null ? newLp.getLinkAddresses() : null);
+
+ List<IpPrefix> unicastLocalPrefixesToBeAdded = new ArrayList<>();
+ List<IpPrefix> unicastLocalPrefixesToBeRemoved = new ArrayList<>();
+
+ // Finding the list of local network prefixes that needs to be added
+ if (newLp != null) {
+ for (LinkAddress linkAddress : newLp.getLinkAddresses()) {
+ unicastLocalPrefixesToBeAdded.addAll(
+ getLocalNetworkPrefixesForAddress(linkAddress));
+ }
+ }
+
+ for (LinkAddress linkAddress : linkAddressDiff.removed) {
+ unicastLocalPrefixesToBeRemoved.addAll(getLocalNetworkPrefixesForAddress(linkAddress));
+ }
+
+ // If newLp is not null, adding local network prefixes using interface name of newLp
+ if (newLp != null) {
+ addLocalAddressesToBpfMap(newLp.getInterfaceName(),
+ new ArrayList<>(unicastLocalPrefixesToBeAdded), newLp);
+ }
+ if (oldLp != null) {
+ // excluding removal of ip prefixes that needs to be added for newLp, but also
+ // removed for oldLp.
+ if (newLp != null && Objects.equals(oldLp.getInterfaceName(),
+ newLp.getInterfaceName())) {
+ unicastLocalPrefixesToBeRemoved.removeAll(unicastLocalPrefixesToBeAdded);
+ }
+ // removing ip local network prefixes because of change in link addresses.
+ removeLocalAddressesFromBpfMap(oldLp.getInterfaceName(),
+ new ArrayList<>(unicastLocalPrefixesToBeRemoved), oldLp);
+ }
+
+ }
+
+ /**
+ * Filters IpPrefix that are local prefixes and LinkAddress is part of them.
+ * @param linkAddress link address used for filtering
+ * @return list of IpPrefix that are local addresses.
+ */
+ private List<IpPrefix> getLocalNetworkPrefixesForAddress(LinkAddress linkAddress) {
+ List<IpPrefix> localPrefixes = new ArrayList<>();
+ if (linkAddress.isIpv6()) {
+ // For IPv6, if the prefix length is greater than zero then they are part of local
+ // network
+ if (linkAddress.getPrefixLength() != 0) {
+ localPrefixes.add(
+ new IpPrefix(linkAddress.getAddress(), linkAddress.getPrefixLength()));
+ }
+ } else {
+ // For IPv4, if the linkAddress is part of IpPrefix adding prefix to result.
+ for (IpPrefix ipv4LocalPrefix : IPV4_LOCAL_PREFIXES) {
+ if (ipv4LocalPrefix.containsPrefix(
+ new IpPrefix(linkAddress.getAddress(), linkAddress.getPrefixLength()))) {
+ localPrefixes.add(ipv4LocalPrefix);
+ }
+ }
+ }
+ return localPrefixes;
+ }
+
+ /**
+ * Adds list of prefixes(addresses) to local network access map.
+ * @param iface interface name
+ * @param prefixes list of prefixes/addresses
+ * @param lp LinkProperties
+ */
+ private void addLocalAddressesToBpfMap(final String iface, final List<IpPrefix> prefixes,
+ @Nullable final LinkProperties lp) {
+ if (!BpfNetMaps.isAtLeast25Q2()) return;
+
+ for (IpPrefix prefix : prefixes) {
+ // Add local dnses allow rule To BpfMap before adding the block rule for prefix
+ addLocalDnsesToBpfMap(iface, prefix, lp);
+ /*
+ Prefix length is used by LPM trie map(local_net_access_map) for performing longest
+ prefix matching, this length represents the maximum number of bits used for matching.
+ The interface index should always be matched which is 32-bit integer. For IPv6, prefix
+ length is calculated by adding the ip address prefix length along with interface index
+ making it (32 + length). IPv4 addresses are stored as ipv4-mapped-ipv6 which implies
+ first 96 bits are common for all ipv4 addresses. Hence, prefix length is calculated as
+ 32(interface index) + 96 (common for ipv4-mapped-ipv6) + length.
+ */
+ final int prefixLengthConstant = (prefix.isIPv4() ? (32 + 96) : 32);
+ mBpfNetMaps.addLocalNetAccess(prefixLengthConstant + prefix.getPrefixLength(),
+ iface, prefix.getAddress(), 0, 0, false);
+
+ }
+
+ }
+
+ /**
+ * Removes list of prefixes(addresses) from local network access map.
+ * @param iface interface name
+ * @param prefixes list of prefixes/addresses
+ * @param lp LinkProperties
+ */
+ private void removeLocalAddressesFromBpfMap(final String iface, final List<IpPrefix> prefixes,
+ @Nullable final LinkProperties lp) {
+ if (!BpfNetMaps.isAtLeast25Q2()) return;
+
+ for (IpPrefix prefix : prefixes) {
+ // The reasoning for prefix length is explained in addLocalAddressesToBpfMap()
+ final int prefixLengthConstant = (prefix.isIPv4() ? (32 + 96) : 32);
+ mBpfNetMaps.removeLocalNetAccess(prefixLengthConstant
+ + prefix.getPrefixLength(), iface, prefix.getAddress(), 0, 0);
+
+ // Also remove the allow rule for dnses included in the prefix after removing the block
+ // rule for prefix.
+ removeLocalDnsesFromBpfMap(iface, prefix, lp);
+ }
+ }
+
+ /**
+ * Adds DNS servers to local network access map, if included in the interface prefix
+ * @param iface interface name
+ * @param prefix IpPrefix
+ * @param lp LinkProperties
+ */
+ private void addLocalDnsesToBpfMap(final String iface, IpPrefix prefix,
+ @Nullable final LinkProperties lp) {
+ if (!BpfNetMaps.isAtLeast25Q2() || lp == null) return;
+
+ for (InetAddress dnsServer : lp.getDnsServers()) {
+ // Adds dns allow rule to LocalNetAccessMap for both TCP and UDP protocol at port 53,
+ // if it is a local dns (ie. it falls in the local prefix range).
+ if (prefix.contains(dnsServer)) {
+ mBpfNetMaps.addLocalNetAccess(getIpv4MappedAddressBitLen(), iface, dnsServer,
+ IPPROTO_UDP, 53, true);
+ mBpfNetMaps.addLocalNetAccess(getIpv4MappedAddressBitLen(), iface, dnsServer,
+ IPPROTO_TCP, 53, true);
+ }
+ }
+ }
+
+ /**
+ * Removes DNS servers from local network access map, if included in the interface prefix
+ * @param iface interface name
+ * @param prefix IpPrefix
+ * @param lp LinkProperties
+ */
+ private void removeLocalDnsesFromBpfMap(final String iface, IpPrefix prefix,
+ @Nullable final LinkProperties lp) {
+ if (!BpfNetMaps.isAtLeast25Q2() || lp == null) return;
+
+ for (InetAddress dnsServer : lp.getDnsServers()) {
+ // Removes dns allow rule from LocalNetAccessMap for both TCP and UDP protocol
+ // at port 53, if it is a local dns (ie. it falls in the prefix range).
+ if (prefix.contains(dnsServer)) {
+ mBpfNetMaps.removeLocalNetAccess(getIpv4MappedAddressBitLen(), iface, dnsServer,
+ IPPROTO_UDP, 53);
+ mBpfNetMaps.removeLocalNetAccess(getIpv4MappedAddressBitLen(), iface, dnsServer,
+ IPPROTO_TCP, 53);
+ }
+ }
+ }
+
+ /**
+ * Returns total bit length of an Ipv4 mapped address.
+ */
+ private int getIpv4MappedAddressBitLen() {
+ final int ifaceLen = 32; // bit length of interface
+ final int inetAddressLen = 32 + 96; // length of ipv4 mapped addresses
+ final int portProtocolLen = 32; //16 for port + 16 for protocol;
+ return ifaceLen + inetAddressLen + portProtocolLen;
+ }
+
+ /**
* Have netd update routes from oldLp to newLp.
* @return true if routes changed between oldLp and newLp
*/
diff --git a/service/src/com/android/server/L2capNetworkProvider.java b/service/src/com/android/server/L2capNetworkProvider.java
index 15c860b..d0b0603 100644
--- a/service/src/com/android/server/L2capNetworkProvider.java
+++ b/service/src/com/android/server/L2capNetworkProvider.java
@@ -19,6 +19,7 @@
import static android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_6LOWPAN;
import static android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_ANY;
import static android.net.L2capNetworkSpecifier.PSM_ANY;
+import static android.net.L2capNetworkSpecifier.ROLE_CLIENT;
import static android.net.L2capNetworkSpecifier.ROLE_SERVER;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
@@ -36,6 +37,7 @@
import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
@@ -54,6 +56,7 @@
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.system.Os;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -62,6 +65,9 @@
import com.android.server.net.L2capNetwork;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
@@ -88,9 +94,9 @@
private final NetworkProvider mProvider;
private final BlanketReservationOffer mBlanketOffer;
private final Set<ReservedServerOffer> mReservedServerOffers = new ArraySet<>();
- // mBluetoothManager guaranteed non-null when read on handler thread after start() is called
- @Nullable
- private BluetoothManager mBluetoothManager;
+ private final ClientOffer mClientOffer;
+ private final BluetoothManager mBluetoothManager;
+ private final boolean mIsSupported;
// Note: IFNAMSIZ is 16.
private static final String TUN_IFNAME = "l2cap-tun";
@@ -269,16 +275,117 @@
return new L2capNetwork(mHandler, mContext, mProvider, ifname, socket, tunFd, caps, cb);
}
+ private static void closeBluetoothSocket(BluetoothSocket socket) {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to close BluetoothSocket", e);
+ }
+ }
private class ReservedServerOffer implements NetworkOfferCallback {
private final NetworkCapabilities mReservedCapabilities;
- private final BluetoothServerSocket mServerSocket;
+ private final AcceptThread mAcceptThread;
+ // This set should almost always contain at most one network. This is because all L2CAP
+ // server networks created by the same reserved offer are indistinguishable from each other,
+ // so that ConnectivityService will tear down all but the first. However, temporarily, there
+ // can be more than one network.
+ private final Set<L2capNetwork> mL2capNetworks = new ArraySet<>();
+
+ private class AcceptThread extends Thread {
+ private static final int TIMEOUT_MS = 500;
+ private final BluetoothServerSocket mServerSocket;
+ private volatile boolean mIsRunning = true;
+
+ public AcceptThread(BluetoothServerSocket serverSocket) {
+ mServerSocket = serverSocket;
+ }
+
+ private void postDestroyAndUnregisterReservedOffer() {
+ mHandler.post(() -> {
+ destroyAndUnregisterReservedOffer(ReservedServerOffer.this);
+ });
+ }
+
+ private void postCreateServerNetwork(BluetoothSocket connectedSocket) {
+ mHandler.post(() -> {
+ final boolean success = createServerNetwork(connectedSocket);
+ if (!success) closeBluetoothSocket(connectedSocket);
+ });
+ }
+
+ public void run() {
+ while (mIsRunning) {
+ final BluetoothSocket connectedSocket;
+ try {
+ connectedSocket = mServerSocket.accept();
+ } catch (IOException e) {
+ // BluetoothServerSocket was closed().
+ if (!mIsRunning) return;
+
+ // Else, BluetoothServerSocket encountered exception.
+ Log.e(TAG, "BluetoothServerSocket#accept failed", e);
+ postDestroyAndUnregisterReservedOffer();
+ return; // stop running immediately on error
+ }
+ postCreateServerNetwork(connectedSocket);
+ }
+ }
+
+ public void tearDown() {
+ mIsRunning = false;
+ try {
+ // BluetoothServerSocket.close() is thread-safe.
+ mServerSocket.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to close BluetoothServerSocket", e);
+ }
+ try {
+ join();
+ } catch (InterruptedException e) {
+ // join() interrupted during tearDown(). Do nothing.
+ }
+ }
+ }
+
+ private boolean createServerNetwork(BluetoothSocket socket) {
+ // It is possible the offer went away.
+ if (!mReservedServerOffers.contains(this)) return false;
+
+ if (!socket.isConnected()) {
+ Log.wtf(TAG, "BluetoothSocket must be connected");
+ return false;
+ }
+
+ final L2capNetwork network = createL2capNetwork(socket, mReservedCapabilities,
+ new L2capNetwork.ICallback() {
+ @Override
+ public void onError(L2capNetwork network) {
+ destroyAndUnregisterReservedOffer(ReservedServerOffer.this);
+ }
+ @Override
+ public void onNetworkUnwanted(L2capNetwork network) {
+ // Leave reservation in place.
+ final boolean networkExists = mL2capNetworks.remove(network);
+ if (!networkExists) return; // already torn down.
+ network.tearDown();
+ }
+ });
+
+ if (network == null) {
+ Log.e(TAG, "Failed to create L2capNetwork");
+ return false;
+ }
+
+ mL2capNetworks.add(network);
+ return true;
+ }
public ReservedServerOffer(NetworkCapabilities reservedCapabilities,
BluetoothServerSocket serverSocket) {
mReservedCapabilities = reservedCapabilities;
- // TODO: ServerSocket will be managed by an AcceptThread.
- mServerSocket = serverSocket;
+ mAcceptThread = new AcceptThread(serverSocket);
+ mAcceptThread.start();
}
public NetworkCapabilities getReservedCapabilities() {
@@ -287,25 +394,261 @@
@Override
public void onNetworkNeeded(NetworkRequest request) {
- // TODO: implement
+ // UNUSED: the lifetime of the reserved network is controlled by the blanket offer.
}
@Override
public void onNetworkUnneeded(NetworkRequest request) {
- // TODO: implement
+ // UNUSED: the lifetime of the reserved network is controlled by the blanket offer.
+ }
+
+ /** Called when the reservation goes away and the reserved offer must be torn down. */
+ public void tearDown() {
+ mAcceptThread.tearDown();
+ for (L2capNetwork network : mL2capNetworks) {
+ network.tearDown();
+ }
+ }
+ }
+
+ private class ClientOffer implements NetworkOfferCallback {
+ public static final NetworkScore SCORE = new NetworkScore.Builder().build();
+ public static final NetworkCapabilities CAPABILITIES;
+ static {
+ // Below capabilities will match any request with an L2capNetworkSpecifier
+ // that specifies ROLE_CLIENT or without a NetworkSpecifier.
+ final L2capNetworkSpecifier l2capNetworkSpecifier = new L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_CLIENT)
+ .build();
+ CAPABILITIES = new NetworkCapabilities.Builder(COMMON_CAPABILITIES)
+ .setNetworkSpecifier(l2capNetworkSpecifier)
+ .build();
+ }
+
+ private final Map<L2capNetworkSpecifier, ClientRequestInfo> mClientNetworkRequests =
+ new ArrayMap<>();
+
+ /**
+ * State object to store information for client NetworkRequests.
+ */
+ private static class ClientRequestInfo {
+ public final L2capNetworkSpecifier specifier;
+ public final List<NetworkRequest> requests = new ArrayList<>();
+ // TODO: add support for retries.
+ public final ConnectThread connectThread;
+ @Nullable
+ public L2capNetwork network;
+
+ public ClientRequestInfo(NetworkRequest request, ConnectThread connectThread) {
+ this.specifier = (L2capNetworkSpecifier) request.getNetworkSpecifier();
+ this.requests.add(request);
+ this.connectThread = connectThread;
+ }
+ }
+
+ // TODO: consider using ExecutorService
+ private class ConnectThread extends Thread {
+ private final L2capNetworkSpecifier mSpecifier;
+ private final BluetoothSocket mSocket;
+ private volatile boolean mIsAborted = false;
+
+ public ConnectThread(L2capNetworkSpecifier specifier, BluetoothSocket socket) {
+ mSpecifier = specifier;
+ mSocket = socket;
+ }
+
+ public void run() {
+ try {
+ mSocket.connect();
+ mHandler.post(() -> {
+ final boolean success = createClientNetwork(mSpecifier, mSocket);
+ if (!success) closeBluetoothSocket(mSocket);
+ });
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to connect", e);
+ if (mIsAborted) return;
+
+ closeBluetoothSocket(mSocket);
+ mHandler.post(() -> {
+ declareAllNetworkRequestsUnfulfillable(mSpecifier);
+ });
+ }
+ }
+
+ public void abort() {
+ mIsAborted = true;
+ // Closing the BluetoothSocket is the only way to unblock connect() because it calls
+ // shutdown on the underlying (connected) SOCK_SEQPACKET.
+ // It is safe to call BluetoothSocket#close() multiple times.
+ closeBluetoothSocket(mSocket);
+ try {
+ join();
+ } catch (InterruptedException e) {
+ Log.i(TAG, "Interrupted while joining ConnectThread", e);
+ }
+ }
+ }
+
+ private boolean createClientNetwork(L2capNetworkSpecifier specifier,
+ BluetoothSocket socket) {
+ // Check whether request still exists
+ final ClientRequestInfo cri = mClientNetworkRequests.get(specifier);
+ if (cri == null) return false;
+
+ final NetworkCapabilities caps = new NetworkCapabilities.Builder(CAPABILITIES)
+ .setNetworkSpecifier(specifier)
+ .build();
+
+ final L2capNetwork network = createL2capNetwork(socket, caps,
+ new L2capNetwork.ICallback() {
+ // TODO: do not send onUnavailable() after the network has become available. The
+ // right thing to do here is to tearDown the network (if it still exists, because
+ // note that the request might have already been removed in the meantime, so
+ // `network` cannot be used directly.
+ @Override
+ public void onError(L2capNetwork network) {
+ declareAllNetworkRequestsUnfulfillable(specifier);
+ }
+ @Override
+ public void onNetworkUnwanted(L2capNetwork network) {
+ declareAllNetworkRequestsUnfulfillable(specifier);
+ }
+ });
+ if (network == null) return false;
+
+ cri.network = network;
+ return true;
+ }
+
+ private boolean isValidL2capSpecifier(@Nullable NetworkSpecifier spec) {
+ if (spec == null) return false;
+
+ // If not null, guaranteed to be L2capNetworkSepcifier.
+ final L2capNetworkSpecifier l2capSpec = (L2capNetworkSpecifier) spec;
+
+ // The ROLE_CLIENT offer can be satisfied by a ROLE_ANY request.
+ if (l2capSpec.getRole() != ROLE_CLIENT) return false;
+
+ // HEADER_COMPRESSION_ANY is never valid in a request.
+ if (l2capSpec.getHeaderCompression() == HEADER_COMPRESSION_ANY) return false;
+
+ // remoteAddr must not be null for ROLE_CLIENT requests.
+ if (l2capSpec.getRemoteAddress() == null) return false;
+
+ // Client network requests require a PSM to be specified.
+ // Ensure the PSM is within the valid range of dynamic BLE L2CAP values.
+ if (l2capSpec.getPsm() < 0x80) return false;
+ if (l2capSpec.getPsm() > 0xFF) return false;
+
+ return true;
+ }
+
+ @Override
+ public void onNetworkNeeded(NetworkRequest request) {
+ Log.d(TAG, "New client network request: " + request);
+ if (!isValidL2capSpecifier(request.getNetworkSpecifier())) {
+ Log.w(TAG, "Ignoring invalid client request: " + request);
+ return;
+ }
+
+ final L2capNetworkSpecifier requestSpecifier =
+ (L2capNetworkSpecifier) request.getNetworkSpecifier();
+ // Check whether this exact request is already being tracked.
+ final ClientRequestInfo cri = mClientNetworkRequests.get(requestSpecifier);
+ if (cri != null) {
+ Log.d(TAG, "The request is already being tracked. NetworkRequest: " + request);
+ cri.requests.add(request);
+ return;
+ }
+
+ // Check whether a fuzzy match shows a mismatch in header compression by calling
+ // canBeSatisfiedBy().
+ // TODO: Add a copy constructor to L2capNetworkSpecifier.Builder.
+ final L2capNetworkSpecifier matchAnyHeaderCompressionSpecifier =
+ new L2capNetworkSpecifier.Builder()
+ .setRole(requestSpecifier.getRole())
+ .setRemoteAddress(requestSpecifier.getRemoteAddress())
+ .setPsm(requestSpecifier.getPsm())
+ .setHeaderCompression(HEADER_COMPRESSION_ANY)
+ .build();
+ for (L2capNetworkSpecifier existingSpecifier : mClientNetworkRequests.keySet()) {
+ if (existingSpecifier.canBeSatisfiedBy(matchAnyHeaderCompressionSpecifier)) {
+ // This requeset can never be serviced as this network already exists with a
+ // different header compression mechanism.
+ mProvider.declareNetworkRequestUnfulfillable(request);
+ return;
+ }
+ }
+
+ // If the code reaches here, this is a new request.
+ final BluetoothAdapter bluetoothAdapter = mBluetoothManager.getAdapter();
+ if (bluetoothAdapter == null) {
+ Log.w(TAG, "Failed to get BluetoothAdapter");
+ mProvider.declareNetworkRequestUnfulfillable(request);
+ return;
+ }
+
+ final byte[] macAddress = requestSpecifier.getRemoteAddress().toByteArray();
+ final BluetoothDevice bluetoothDevice = bluetoothAdapter.getRemoteDevice(macAddress);
+ final BluetoothSocket socket;
+ try {
+ socket = bluetoothDevice.createInsecureL2capChannel(requestSpecifier.getPsm());
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to createInsecureL2capChannel", e);
+ mProvider.declareNetworkRequestUnfulfillable(request);
+ return;
+ }
+
+ final ConnectThread connectThread = new ConnectThread(requestSpecifier, socket);
+ connectThread.start();
+ final ClientRequestInfo newRequestInfo = new ClientRequestInfo(request, connectThread);
+ mClientNetworkRequests.put(requestSpecifier, newRequestInfo);
+ }
+
+ @Override
+ public void onNetworkUnneeded(NetworkRequest request) {
+ final L2capNetworkSpecifier specifier =
+ (L2capNetworkSpecifier) request.getNetworkSpecifier();
+
+ // Map#get() is safe to call with null key
+ final ClientRequestInfo cri = mClientNetworkRequests.get(specifier);
+ if (cri == null) return;
+
+ cri.requests.remove(request);
+ if (cri.requests.size() > 0) return;
+
+ // If the code reaches here, the network needs to be torn down.
+ releaseClientNetworkRequest(cri);
}
/**
- * Called when the reservation goes away and the reserved offer must be torn down.
+ * Release the client network request and tear down all associated state.
*
- * This method can be called multiple times.
+ * Only call this when all associated NetworkRequests have been released.
*/
- public void tearDown() {
- try {
- mServerSocket.close();
- } catch (IOException e) {
- Log.w(TAG, "Failed to close BluetoothServerSocket", e);
+ private void releaseClientNetworkRequest(ClientRequestInfo cri) {
+ mClientNetworkRequests.remove(cri.specifier);
+ if (cri.connectThread.isAlive()) {
+ // Note that if ConnectThread succeeds between calling #isAlive() and #abort(), the
+ // request will already be removed from mClientNetworkRequests by the time the
+ // createClientNetwork() call is processed on the handler, so it is safe to call
+ // #abort().
+ cri.connectThread.abort();
}
+
+ if (cri.network != null) {
+ cri.network.tearDown();
+ }
+ }
+
+ private void declareAllNetworkRequestsUnfulfillable(L2capNetworkSpecifier specifier) {
+ final ClientRequestInfo cri = mClientNetworkRequests.get(specifier);
+ if (cri == null) return;
+
+ for (NetworkRequest request : cri.requests) {
+ mProvider.declareNetworkRequestUnfulfillable(request);
+ }
+ releaseClientNetworkRequest(cri);
}
}
@@ -336,6 +679,9 @@
mHandler = new Handler(mHandlerThread.getLooper());
mProvider = mDeps.getNetworkProvider(context, mHandlerThread.getLooper());
mBlanketOffer = new BlanketReservationOffer();
+ mClientOffer = new ClientOffer();
+ mBluetoothManager = context.getSystemService(BluetoothManager.class);
+ mIsSupported = mContext.getPackageManager().hasSystemFeature(FEATURE_BLUETOOTH_LE);
}
/**
@@ -344,20 +690,24 @@
* Called on CS Handler thread.
*/
public void start() {
+ if (!mIsSupported) {
+ // In order to make mHandler final, the HandlerThread needs to be started before
+ // HandlerThread.getLooper() is called during the construction of the Handler.
+ mHandlerThread.quitSafely();
+ try {
+ mHandlerThread.join();
+ } catch (InterruptedException e) {
+ // join() interrupted. Do nothing.
+ }
+ return;
+ }
+
mHandler.post(() -> {
- final PackageManager pm = mContext.getPackageManager();
- if (!pm.hasSystemFeature(FEATURE_BLUETOOTH_LE)) {
- return;
- }
- mBluetoothManager = mContext.getSystemService(BluetoothManager.class);
- if (mBluetoothManager == null) {
- // Can this ever happen?
- Log.wtf(TAG, "BluetoothManager not found");
- return;
- }
mContext.getSystemService(ConnectivityManager.class).registerNetworkProvider(mProvider);
mProvider.registerNetworkOffer(BlanketReservationOffer.SCORE,
BlanketReservationOffer.CAPABILITIES, mHandler::post, mBlanketOffer);
+ mProvider.registerNetworkOffer(ClientOffer.SCORE,
+ ClientOffer.CAPABILITIES, mHandler::post, mClientOffer);
});
}
}
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index 0d388e8..737e27a 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -282,7 +282,8 @@
mContext = context;
mBpfNetMaps = bpfNetMaps;
mThread = thread;
- if (Flags.restrictLocalNetwork()) {
+ if (BpfNetMaps.isAtLeast25Q2()) {
+ // Local net restrictions is supported as a developer opt-in starting in Android B.
// This listener should finish registration by the time the system has completed
// boot setup such that any changes to runtime permissions for local network
// restrictions can only occur after this registration has completed.
@@ -1330,7 +1331,8 @@
// Flags.restrictLocalNetwork() is used to offer the feature to devices, but it will
// only be enforced when develoeprs choose to enable it.
// TODO(b/394567896): Update compat change checks
- if (CompatChanges.isChangeEnabled(RESTRICT_LOCAL_NETWORK, uid)) {
+ if (CompatChanges.isChangeEnabled(RESTRICT_LOCAL_NETWORK, uid)
+ && BpfNetMaps.isAtLeast25Q2()) {
// TODO(b/388803658): Update network permissions and record change
}
}
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index 026e985..8d7ae5c 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -23,6 +23,7 @@
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.util.List;
/**
* Network constants used by the network stack.
@@ -341,6 +342,26 @@
*/
public static final String TEST_URL_EXPIRATION_TIME = "test_url_expiration_time";
+ /**
+ * List of IpPrefix that are local network prefixes.
+ */
+ public static final List<IpPrefix> IPV4_LOCAL_PREFIXES = List.of(
+ new IpPrefix("169.254.0.0/16"), // Link Local
+ new IpPrefix("100.64.0.0/10"), // CGNAT
+ new IpPrefix("10.0.0.0/8"), // RFC1918
+ new IpPrefix("172.16.0.0/12"), // RFC1918
+ new IpPrefix("192.168.0.0/16") // RFC1918
+ );
+
+ /**
+ * List of IpPrefix that are multicast and broadcast prefixes.
+ */
+ public static final List<IpPrefix> MULTICAST_AND_BROADCAST_PREFIXES = List.of(
+ new IpPrefix("224.0.0.0/4"), // Multicast
+ new IpPrefix("ff00::/8"), // Multicast
+ new IpPrefix("255.255.255.255/32") // Broadcast
+ );
+
// TODO: Move to Inet4AddressUtils
// See aosp/1455936: NetworkStackConstants can't depend on it as it causes jarjar-related issues
// for users of both the net-utils-device-common and net-utils-framework-common libraries.
diff --git a/staticlibs/testutils/host/python/tether_utils.py b/staticlibs/testutils/host/python/tether_utils.py
index c63de1f..710f8a8 100644
--- a/staticlibs/testutils/host/python/tether_utils.py
+++ b/staticlibs/testutils/host/python/tether_utils.py
@@ -95,7 +95,9 @@
hotspot_interface = server.startHotspot(test_ssid, test_passphrase)
# Make the client connects to the hotspot.
- client_network = client.connectToWifi(test_ssid, test_passphrase)
+ client_network = client.connectToWifi(
+ test_ssid, test_passphrase, upstream_type != UpstreamType.NONE
+ )
return hotspot_interface, client_network
diff --git a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
index 6da7e9a..252052e 100644
--- a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
+++ b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
@@ -27,13 +27,11 @@
import android.net.NetworkRequest
import android.net.cts.util.CtsNetUtils
import android.net.cts.util.CtsTetheringUtils
-import android.net.wifi.ScanResult
import android.net.wifi.SoftApConfiguration
import android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
import android.net.wifi.WifiConfiguration
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
-import android.net.wifi.WifiNetworkSpecifier
import android.net.wifi.WifiSsid
import androidx.test.platform.app.InstrumentationRegistry
import com.android.compatibility.common.util.PropertyUtil
@@ -109,10 +107,7 @@
// Suppress warning because WifiManager methods to connect to a config are
// documented not to be deprecated for privileged users.
@Suppress("DEPRECATION")
- fun connectToWifi(ssid: String, passphrase: String): Long {
- val specifier = WifiNetworkSpecifier.Builder()
- .setBand(ScanResult.WIFI_BAND_24_GHZ)
- .build()
+ fun connectToWifi(ssid: String, passphrase: String, requireValidation: Boolean): Long {
val wifiConfig = WifiConfiguration()
wifiConfig.SSID = "\"" + ssid + "\""
wifiConfig.preSharedKey = "\"" + passphrase + "\""
@@ -141,7 +136,8 @@
return@runAsShell networkCallback.eventuallyExpect<CapabilitiesChanged> {
// Remove double quotes.
val ssidFromCaps = (WifiInfo::sanitizeSsid)(it.caps.ssid)
- ssidFromCaps == ssid && it.caps.hasCapability(NET_CAPABILITY_VALIDATED)
+ ssidFromCaps == ssid && (!requireValidation ||
+ it.caps.hasCapability(NET_CAPABILITY_VALIDATED))
}.network.networkHandle
}
}
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
index 9379697..ee2e7db 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -52,8 +52,8 @@
import android.os.Handler
import android.os.HandlerThread
import android.os.PowerManager
-import android.os.UserManager
import android.os.SystemProperties
+import android.os.UserManager
import android.platform.test.annotations.AppModeFull
import android.provider.DeviceConfig
import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index e94d94f..4a21f09 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -375,6 +375,7 @@
}
private static boolean isIpv6UdpEncapSupportedByKernel() {
+ if (SdkLevel.isAtLeastB() && isKernelVersionAtLeast("5.10.0")) return true;
return isKernelVersionAtLeast("5.15.31")
|| (isKernelVersionAtLeast("5.10.108") && !isKernelVersionAtLeast("5.15.0"));
}
@@ -390,8 +391,8 @@
assumeTrue("Not supported by kernel", isIpv6UdpEncapSupportedByKernel());
}
- // TODO: b/319532485 Figure out whether to support x86_32
private static boolean isRequestTransformStateSupportedByKernel() {
+ if (SdkLevel.isAtLeastB()) return true;
return NetworkUtils.isKernel64Bit() || !NetworkUtils.isKernelX86();
}
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index 73eb24d..fd92672 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -269,9 +269,11 @@
public void testAddLocalNetAccessAfterVWithIncorrectInterface() throws Exception {
assertTrue(mLocalNetAccessMap.isEmpty());
+ // wlan2 is an incorrect interface
mBpfNetMaps.addLocalNetAccess(160, "wlan2",
Inet4Address.getByName("196.68.0.0"), 0, 0, true);
+ // As we tried to add incorrect interface, it would be skipped and map should be empty.
assertTrue(mLocalNetAccessMap.isEmpty());
}
@@ -302,6 +304,53 @@
@Test
@IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testRemoveLocalNetAccessBeforeV() {
+ assertThrows(UnsupportedOperationException.class, () ->
+ mBpfNetMaps.removeLocalNetAccess(0, TEST_IF_NAME, Inet6Address.ANY, 0, 0));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testRemoveLocalNetAccessAfterV() throws Exception {
+ assertTrue(mLocalNetAccessMap.isEmpty());
+
+ mBpfNetMaps.addLocalNetAccess(160, TEST_IF_NAME,
+ Inet4Address.getByName("196.68.0.0"), 0, 0, true);
+
+ assertNotNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+ Inet4Address.getByName("196.68.0.0"), 0, 0)));
+ assertNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+ Inet4Address.getByName("100.68.0.0"), 0, 0)));
+
+ mBpfNetMaps.removeLocalNetAccess(160, TEST_IF_NAME,
+ Inet4Address.getByName("196.68.0.0"), 0, 0);
+ assertNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+ Inet4Address.getByName("196.68.0.0"), 0, 0)));
+ assertNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+ Inet4Address.getByName("100.68.0.0"), 0, 0)));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testRemoveLocalNetAccessAfterVWithIncorrectInterface() throws Exception {
+ assertTrue(mLocalNetAccessMap.isEmpty());
+
+ mBpfNetMaps.addLocalNetAccess(160, TEST_IF_NAME,
+ Inet4Address.getByName("196.68.0.0"), 0, 0, true);
+
+ assertNotNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+ Inet4Address.getByName("196.68.0.0"), 0, 0)));
+ assertNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+ Inet4Address.getByName("100.68.0.0"), 0, 0)));
+
+ mBpfNetMaps.removeLocalNetAccess(160, "wlan2",
+ Inet4Address.getByName("196.68.0.0"), 0, 0);
+ assertNotNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+ Inet4Address.getByName("196.68.0.0"), 0, 0)));
+ }
+
+ @Test
+ @IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM)
public void testAddUidToLocalNetBlockMapBeforeV() {
assertThrows(UnsupportedOperationException.class, () ->
mBpfNetMaps.addUidToLocalNetBlockMap(0));
@@ -309,9 +358,9 @@
@Test
@IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM)
- public void testIsUidPresentInLocalNetBlockMapBeforeV() {
+ public void testIsUidBlockedFromUsingLocalNetworkBeforeV() {
assertThrows(UnsupportedOperationException.class, () ->
- mBpfNetMaps.getUidValueFromLocalNetBlockMap(0));
+ mBpfNetMaps.isUidBlockedFromUsingLocalNetwork(0));
}
@Test
@@ -339,18 +388,18 @@
@Test
@IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
- public void testIsUidPresentInLocalNetBlockMapAfterV() throws Exception {
+ public void testIsUidBlockedFromUsingLocalNetworkAfterV() throws Exception {
final int uid0 = TEST_UIDS[0];
final int uid1 = TEST_UIDS[1];
assertTrue(mLocalNetAccessMap.isEmpty());
mLocalNetBlockedUidMap.updateEntry(new U32(uid0), new Bool(true));
- assertTrue(mBpfNetMaps.getUidValueFromLocalNetBlockMap(uid0));
- assertFalse(mBpfNetMaps.getUidValueFromLocalNetBlockMap(uid1));
+ assertTrue(mBpfNetMaps.isUidBlockedFromUsingLocalNetwork(uid0));
+ assertFalse(mBpfNetMaps.isUidBlockedFromUsingLocalNetwork(uid1));
mLocalNetBlockedUidMap.updateEntry(new U32(uid1), new Bool(true));
- assertTrue(mBpfNetMaps.getUidValueFromLocalNetBlockMap(uid1));
+ assertTrue(mBpfNetMaps.isUidBlockedFromUsingLocalNetwork(uid1));
}
@Test
diff --git a/tests/unit/java/com/android/server/L2capNetworkProviderTest.kt b/tests/unit/java/com/android/server/L2capNetworkProviderTest.kt
deleted file mode 100644
index 49eb476..0000000
--- a/tests/unit/java/com/android/server/L2capNetworkProviderTest.kt
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2024 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
-
-import android.bluetooth.BluetoothAdapter
-import android.bluetooth.BluetoothManager
-import android.bluetooth.BluetoothServerSocket
-import android.content.Context
-import android.content.pm.PackageManager
-import android.content.pm.PackageManager.FEATURE_BLUETOOTH_LE
-import android.net.ConnectivityManager
-import android.net.ConnectivityManager.TYPE_NONE
-import android.net.L2capNetworkSpecifier
-import android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_6LOWPAN
-import android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_NONE
-import android.net.L2capNetworkSpecifier.ROLE_SERVER
-import android.net.NetworkCapabilities
-import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH
-import android.net.NetworkProvider
-import android.net.NetworkProvider.NetworkOfferCallback
-import android.net.NetworkRequest
-import android.os.Build
-import android.os.Handler
-import android.os.HandlerThread
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.waitForIdle
-import kotlin.test.assertTrue
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mock
-import org.mockito.Mockito.any
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.never
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-private const val TAG = "L2capNetworkProviderTest"
-private const val TIMEOUT_MS = 1000
-
-private val RESERVATION_CAPS = NetworkCapabilities.Builder.withoutDefaultCapabilities()
- .addTransportType(TRANSPORT_BLUETOOTH)
- .build()
-
-private val RESERVATION = NetworkRequest(
- NetworkCapabilities(RESERVATION_CAPS),
- TYPE_NONE,
- 42 /* rId */,
- NetworkRequest.Type.RESERVATION
-)
-
-@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-class L2capNetworkProviderTest {
- @Mock private lateinit var context: Context
- @Mock private lateinit var deps: L2capNetworkProvider.Dependencies
- @Mock private lateinit var provider: NetworkProvider
- @Mock private lateinit var cm: ConnectivityManager
- @Mock private lateinit var pm: PackageManager
- @Mock private lateinit var bm: BluetoothManager
- @Mock private lateinit var adapter: BluetoothAdapter
- @Mock private lateinit var serverSocket: BluetoothServerSocket
-
- private val handlerThread = HandlerThread("$TAG handler thread").apply { start() }
- private val handler = Handler(handlerThread.looper)
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- doReturn(provider).`when`(deps).getNetworkProvider(any(), any())
- doReturn(handlerThread).`when`(deps).getHandlerThread()
- doReturn(cm).`when`(context).getSystemService(eq(ConnectivityManager::class.java))
- doReturn(pm).`when`(context).getPackageManager()
- doReturn(true).`when`(pm).hasSystemFeature(FEATURE_BLUETOOTH_LE)
-
- doReturn(bm).`when`(context).getSystemService(eq(BluetoothManager::class.java))
- doReturn(adapter).`when`(bm).getAdapter()
- doReturn(serverSocket).`when`(adapter).listenUsingInsecureL2capChannel()
- doReturn(0x80).`when`(serverSocket).getPsm()
- }
-
- @After
- fun tearDown() {
- handlerThread.quitSafely()
- handlerThread.join()
- }
-
- @Test
- fun testNetworkProvider_registeredWhenSupported() {
- L2capNetworkProvider(deps, context).start()
- handlerThread.waitForIdle(TIMEOUT_MS)
- verify(cm).registerNetworkProvider(eq(provider))
- verify(provider).registerNetworkOffer(any(), any(), any(), any())
- }
-
- @Test
- fun testNetworkProvider_notRegisteredWhenNotSupported() {
- doReturn(false).`when`(pm).hasSystemFeature(FEATURE_BLUETOOTH_LE)
- L2capNetworkProvider(deps, context).start()
- handlerThread.waitForIdle(TIMEOUT_MS)
- verify(cm, never()).registerNetworkProvider(eq(provider))
- }
-
- fun doTestBlanketOfferIgnoresRequest(request: NetworkRequest) {
- clearInvocations(provider)
- L2capNetworkProvider(deps, context).start()
- handlerThread.waitForIdle(TIMEOUT_MS)
-
- val blanketOfferCaptor = ArgumentCaptor.forClass(NetworkOfferCallback::class.java)
- verify(provider).registerNetworkOffer(any(), any(), any(), blanketOfferCaptor.capture())
-
- blanketOfferCaptor.value.onNetworkNeeded(request)
- verify(provider).registerNetworkOffer(any(), any(), any(), any())
- }
-
- fun doTestBlanketOfferCreatesReservation(
- request: NetworkRequest,
- reservation: NetworkCapabilities
- ) {
- clearInvocations(provider)
- L2capNetworkProvider(deps, context).start()
- handlerThread.waitForIdle(TIMEOUT_MS)
-
- val blanketOfferCaptor = ArgumentCaptor.forClass(NetworkOfferCallback::class.java)
- verify(provider).registerNetworkOffer(any(), any(), any(), blanketOfferCaptor.capture())
-
- blanketOfferCaptor.value.onNetworkNeeded(request)
-
- val capsCaptor = ArgumentCaptor.forClass(NetworkCapabilities::class.java)
- verify(provider, times(2)).registerNetworkOffer(any(), capsCaptor.capture(), any(), any())
-
- assertTrue(reservation.satisfiedByNetworkCapabilities(capsCaptor.value))
- }
-
- @Test
- fun testBlanketOffer_reservationWithoutSpecifier() {
- val caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
- .addTransportType(TRANSPORT_BLUETOOTH)
- .build()
- val nr = NetworkRequest(caps, TYPE_NONE, 42 /* rId */, NetworkRequest.Type.RESERVATION)
-
- doTestBlanketOfferIgnoresRequest(nr)
- }
-
- @Test
- fun testBlanketOffer_reservationWithCorrectSpecifier() {
- var specifier = L2capNetworkSpecifier.Builder()
- .setRole(ROLE_SERVER)
- .setHeaderCompression(HEADER_COMPRESSION_6LOWPAN)
- .build()
- var caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
- .addTransportType(TRANSPORT_BLUETOOTH)
- .setNetworkSpecifier(specifier)
- .build()
- var nr = NetworkRequest(caps, TYPE_NONE, 42 /* rId */, NetworkRequest.Type.RESERVATION)
- doTestBlanketOfferCreatesReservation(nr, caps)
-
- specifier = L2capNetworkSpecifier.Builder()
- .setRole(ROLE_SERVER)
- .setHeaderCompression(HEADER_COMPRESSION_NONE)
- .build()
- caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
- .addTransportType(TRANSPORT_BLUETOOTH)
- .setNetworkSpecifier(specifier)
- .build()
- nr = NetworkRequest(caps, TYPE_NONE, 43 /* rId */, NetworkRequest.Type.RESERVATION)
- doTestBlanketOfferCreatesReservation(nr, caps)
- }
-
- @Test
- fun testBlanketOffer_reservationWithIncorrectSpecifier() {
- var specifier = L2capNetworkSpecifier.Builder().build()
- var caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
- .addTransportType(TRANSPORT_BLUETOOTH)
- .setNetworkSpecifier(specifier)
- .build()
- var nr = NetworkRequest(caps, TYPE_NONE, 42 /* rId */, NetworkRequest.Type.RESERVATION)
- doTestBlanketOfferIgnoresRequest(nr)
-
- specifier = L2capNetworkSpecifier.Builder()
- .setRole(ROLE_SERVER)
- .build()
- caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
- .addTransportType(TRANSPORT_BLUETOOTH)
- .setNetworkSpecifier(specifier)
- .build()
- nr = NetworkRequest(caps, TYPE_NONE, 44 /* rId */, NetworkRequest.Type.RESERVATION)
- doTestBlanketOfferIgnoresRequest(nr)
-
- specifier = L2capNetworkSpecifier.Builder()
- .setRole(ROLE_SERVER)
- .setHeaderCompression(HEADER_COMPRESSION_NONE)
- .setPsm(0x81)
- .build()
- caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
- .addTransportType(TRANSPORT_BLUETOOTH)
- .setNetworkSpecifier(specifier)
- .build()
- nr = NetworkRequest(caps, TYPE_NONE, 45 /* rId */, NetworkRequest.Type.RESERVATION)
- doTestBlanketOfferIgnoresRequest(nr)
-
- specifier = L2capNetworkSpecifier.Builder()
- .setHeaderCompression(HEADER_COMPRESSION_NONE)
- .build()
- caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
- .addTransportType(TRANSPORT_BLUETOOTH)
- .setNetworkSpecifier(specifier)
- .build()
- nr = NetworkRequest(caps, TYPE_NONE, 47 /* rId */, NetworkRequest.Type.RESERVATION)
- doTestBlanketOfferIgnoresRequest(nr)
- }
-}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
index ba62114..e6e6ecc 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
@@ -92,6 +92,13 @@
network = TEST_NETWORK_1
}
+private val GOOGLECAST_SERVICE = NsdServiceInfo("TestServiceName", "_googlecast._tcp").apply {
+ subtypes = setOf(TEST_SUBTYPE)
+ port = 12345
+ hostAddresses = listOf(TEST_ADDR)
+ network = TEST_NETWORK_1
+}
+
private val SERVICE_1_SUBTYPE = NsdServiceInfo("TestServiceName", "_advertisertest._tcp").apply {
subtypes = setOf(TEST_SUBTYPE)
port = 12345
@@ -143,6 +150,15 @@
OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
)
+private val OFFLOAD_SERVICE_INFO_GOOGLECAST = OffloadServiceInfo(
+ OffloadServiceInfo.Key("TestServiceName", "_googlecast._tcp"),
+ listOf(),
+ "Android_test.local",
+ TEST_OFFLOAD_PACKET1,
+ Int.MAX_VALUE,
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+)
+
private val OFFLOAD_SERVICEINFO_NO_SUBTYPE = OffloadServiceInfo(
OffloadServiceInfo.Key("TestServiceName", "_advertisertest._tcp"),
listOf(),
@@ -189,11 +205,25 @@
fun setUp() {
thread.start()
doReturn(TEST_HOSTNAME).`when`(mockDeps).generateHostname(anyBoolean())
- doReturn(mockInterfaceAdvertiser1).`when`(mockDeps).makeAdvertiser(eq(mockSocket1),
- any(), any(), any(), any(), any(), any(), any()
+ doReturn(mockInterfaceAdvertiser1).`when`(mockDeps).makeAdvertiser(
+ eq(mockSocket1),
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any()
)
- doReturn(mockInterfaceAdvertiser2).`when`(mockDeps).makeAdvertiser(eq(mockSocket2),
- any(), any(), any(), any(), any(), any(), any()
+ doReturn(mockInterfaceAdvertiser2).`when`(mockDeps).makeAdvertiser(
+ eq(mockSocket2),
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any()
)
doReturn(true).`when`(mockInterfaceAdvertiser1).isProbing(anyInt())
doReturn(true).`when`(mockInterfaceAdvertiser2).isProbing(anyInt())
@@ -202,16 +232,21 @@
doReturn(TEST_INTERFACE1).`when`(mockInterfaceAdvertiser1).socketInterfaceName
doReturn(TEST_INTERFACE2).`when`(mockInterfaceAdvertiser2).socketInterfaceName
doReturn(TEST_OFFLOAD_PACKET1).`when`(mockInterfaceAdvertiser1).getRawOffloadPayload(
- SERVICE_ID_1)
+ SERVICE_ID_1
+ )
doReturn(TEST_OFFLOAD_PACKET1).`when`(mockInterfaceAdvertiser1).getRawOffloadPayload(
- SERVICE_ID_2)
+ SERVICE_ID_2
+ )
doReturn(TEST_OFFLOAD_PACKET1).`when`(mockInterfaceAdvertiser1).getRawOffloadPayload(
- SERVICE_ID_3)
+ SERVICE_ID_3
+ )
doReturn(TEST_OFFLOAD_PACKET1).`when`(mockInterfaceAdvertiser2).getRawOffloadPayload(
- SERVICE_ID_1)
+ SERVICE_ID_1
+ )
doReturn(resources).`when`(context).getResources()
doReturn(SERVICES_PRIORITY_LIST).`when`(resources).getStringArray(
- R.array.config_nsdOffloadServicesPriority)
+ R.array.config_nsdOffloadServicesPriority
+ )
ConnectivityResources.setResourcesContextForTest(context)
}
@@ -232,8 +267,12 @@
fun testAddService_OneNetwork() {
val advertiser =
MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
- postSync { advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1,
- DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
+ postSync { advertiser.addOrUpdateService(
+ SERVICE_ID_1,
+ SERVICE_1,
+ DEFAULT_ADVERTISING_OPTION,
+ TEST_CLIENT_UID_1
+ ) }
val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(TEST_NETWORK_1), socketCbCaptor.capture())
@@ -255,7 +294,9 @@
doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
postSync { intAdvCbCaptor.value.onServiceProbingSucceeded(
- mockInterfaceAdvertiser1, SERVICE_ID_1) }
+ mockInterfaceAdvertiser1,
+ SERVICE_ID_1
+ ) }
verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1), argThat { it.matches(SERVICE_1) })
verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO_NO_SUBTYPE))
@@ -296,12 +337,18 @@
fun testAddService_AllNetworksWithSubType() {
val advertiser =
MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
- postSync { advertiser.addOrUpdateService(SERVICE_ID_1, ALL_NETWORKS_SERVICE_SUBTYPE,
- DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
+ postSync { advertiser.addOrUpdateService(
+ SERVICE_ID_1,
+ ALL_NETWORKS_SERVICE_SUBTYPE,
+ DEFAULT_ADVERTISING_OPTION,
+ TEST_CLIENT_UID_1
+ ) }
val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
- verify(socketProvider).requestSocket(eq(ALL_NETWORKS_SERVICE_SUBTYPE.network),
- socketCbCaptor.capture())
+ verify(socketProvider).requestSocket(
+ eq(ALL_NETWORKS_SERVICE_SUBTYPE.network),
+ socketCbCaptor.capture()
+ )
val socketCb = socketCbCaptor.value
postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_1, mockSocket1, listOf(TEST_LINKADDR)) }
@@ -309,30 +356,56 @@
val intAdvCbCaptor1 = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
val intAdvCbCaptor2 = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
- verify(mockDeps).makeAdvertiser(eq(mockSocket1), eq(listOf(TEST_LINKADDR)),
- eq(thread.looper), any(), intAdvCbCaptor1.capture(), eq(TEST_HOSTNAME), any(), any()
+ verify(mockDeps).makeAdvertiser(
+ eq(mockSocket1),
+ eq(listOf(TEST_LINKADDR)),
+ eq(thread.looper),
+ any(),
+ intAdvCbCaptor1.capture(),
+ eq(TEST_HOSTNAME),
+ any(),
+ any()
)
- verify(mockDeps).makeAdvertiser(eq(mockSocket2), eq(listOf(TEST_LINKADDR)),
- eq(thread.looper), any(), intAdvCbCaptor2.capture(), eq(TEST_HOSTNAME), any(), any()
+ verify(mockDeps).makeAdvertiser(
+ eq(mockSocket2),
+ eq(listOf(TEST_LINKADDR)),
+ eq(thread.looper),
+ any(),
+ intAdvCbCaptor2.capture(),
+ eq(TEST_HOSTNAME),
+ any(),
+ any()
)
verify(mockInterfaceAdvertiser1).addService(
- anyInt(), eq(ALL_NETWORKS_SERVICE_SUBTYPE), any())
+ anyInt(),
+ eq(ALL_NETWORKS_SERVICE_SUBTYPE),
+ any()
+ )
verify(mockInterfaceAdvertiser2).addService(
- anyInt(), eq(ALL_NETWORKS_SERVICE_SUBTYPE), any())
+ anyInt(),
+ eq(ALL_NETWORKS_SERVICE_SUBTYPE),
+ any()
+ )
doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
postSync { intAdvCbCaptor1.value.onServiceProbingSucceeded(
- mockInterfaceAdvertiser1, SERVICE_ID_1) }
+ mockInterfaceAdvertiser1,
+ SERVICE_ID_1
+ ) }
verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO))
// Need both advertisers to finish probing and call onRegisterServiceSucceeded
verify(cb, never()).onRegisterServiceSucceeded(anyInt(), any())
doReturn(false).`when`(mockInterfaceAdvertiser2).isProbing(SERVICE_ID_1)
postSync { intAdvCbCaptor2.value.onServiceProbingSucceeded(
- mockInterfaceAdvertiser2, SERVICE_ID_1) }
+ mockInterfaceAdvertiser2,
+ SERVICE_ID_1
+ ) }
verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE2), eq(OFFLOAD_SERVICEINFO))
- verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1),
- argThat { it.matches(ALL_NETWORKS_SERVICE_SUBTYPE) })
+ verify(cb).onRegisterServiceSucceeded(
+ eq(SERVICE_ID_1),
+ argThat { it.matches(ALL_NETWORKS_SERVICE_SUBTYPE) }
+ )
// Services are conflicted.
postSync {
@@ -378,19 +451,30 @@
val advertiser =
MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
postSync {
- advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1, DEFAULT_ADVERTISING_OPTION,
- TEST_CLIENT_UID_1)
- advertiser.addOrUpdateService(SERVICE_ID_2,
+ advertiser.addOrUpdateService(
+ SERVICE_ID_1,
+ SERVICE_1,
+ DEFAULT_ADVERTISING_OPTION,
+ TEST_CLIENT_UID_1
+ )
+ advertiser.addOrUpdateService(
+ SERVICE_ID_2,
NsdServiceInfo("TestService2", "_PRIORITYTEST._udp").apply {
port = 12345
hostAddresses = listOf(TEST_ADDR)
- }, DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1)
+ },
+ DEFAULT_ADVERTISING_OPTION,
+ TEST_CLIENT_UID_1
+ )
advertiser.addOrUpdateService(
SERVICE_ID_3,
NsdServiceInfo("TestService3", "_notprioritized._tcp").apply {
port = 12345
hostAddresses = listOf(TEST_ADDR)
- }, DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1)
+ },
+ DEFAULT_ADVERTISING_OPTION,
+ TEST_CLIENT_UID_1
+ )
}
val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
@@ -400,8 +484,15 @@
postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_1, mockSocket1, listOf(TEST_LINKADDR)) }
val intAdvCbCaptor1 = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
- verify(mockDeps).makeAdvertiser(eq(mockSocket1), eq(listOf(TEST_LINKADDR)),
- eq(thread.looper), any(), intAdvCbCaptor1.capture(), eq(TEST_HOSTNAME), any(), any()
+ verify(mockDeps).makeAdvertiser(
+ eq(mockSocket1),
+ eq(listOf(TEST_LINKADDR)),
+ eq(thread.looper),
+ any(),
+ intAdvCbCaptor1.capture(),
+ eq(TEST_HOSTNAME),
+ any(),
+ any()
)
doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
@@ -433,30 +524,88 @@
}
@Test
+ fun testAddService_NoSubtypeForGoogleCastOffload() {
+ val advertiser =
+ MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
+ postSync {
+ advertiser.addOrUpdateService(
+ SERVICE_ID_1,
+ GOOGLECAST_SERVICE,
+ DEFAULT_ADVERTISING_OPTION,
+ TEST_CLIENT_UID_1
+ )
+ }
+ val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
+ verify(socketProvider).requestSocket(eq(SERVICE_1.network), socketCbCaptor.capture())
+
+ val socketCb = socketCbCaptor.value
+ postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_1, mockSocket1, listOf(TEST_LINKADDR)) }
+
+ val intAdvCbCaptor1 = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
+ verify(mockDeps).makeAdvertiser(
+ eq(mockSocket1),
+ eq(listOf(TEST_LINKADDR)),
+ eq(thread.looper),
+ any(),
+ intAdvCbCaptor1.capture(),
+ eq(TEST_HOSTNAME),
+ any(),
+ any()
+ )
+
+ doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
+ postSync {
+ intAdvCbCaptor1.value.onServiceProbingSucceeded(mockInterfaceAdvertiser1, SERVICE_ID_1)
+ }
+
+ verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICE_INFO_GOOGLECAST))
+ }
+
+ @Test
fun testAddService_Conflicts() {
val advertiser =
MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
- postSync { advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1,
- DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
+ postSync { advertiser.addOrUpdateService(
+ SERVICE_ID_1,
+ SERVICE_1,
+ DEFAULT_ADVERTISING_OPTION,
+ TEST_CLIENT_UID_1
+ ) }
val oneNetSocketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(TEST_NETWORK_1), oneNetSocketCbCaptor.capture())
val oneNetSocketCb = oneNetSocketCbCaptor.value
// Register a service with the same name on all networks (name conflict)
- postSync { advertiser.addOrUpdateService(SERVICE_ID_2, ALL_NETWORKS_SERVICE,
- DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
+ postSync { advertiser.addOrUpdateService(
+ SERVICE_ID_2,
+ ALL_NETWORKS_SERVICE,
+ DEFAULT_ADVERTISING_OPTION,
+ TEST_CLIENT_UID_1
+ ) }
val allNetSocketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(null), allNetSocketCbCaptor.capture())
val allNetSocketCb = allNetSocketCbCaptor.value
- postSync { advertiser.addOrUpdateService(LONG_SERVICE_ID_1, LONG_SERVICE_1,
- DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
- postSync { advertiser.addOrUpdateService(LONG_SERVICE_ID_2, LONG_ALL_NETWORKS_SERVICE,
- DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
+ postSync { advertiser.addOrUpdateService(
+ LONG_SERVICE_ID_1,
+ LONG_SERVICE_1,
+ DEFAULT_ADVERTISING_OPTION,
+ TEST_CLIENT_UID_1
+ ) }
+ postSync { advertiser.addOrUpdateService(
+ LONG_SERVICE_ID_2,
+ LONG_ALL_NETWORKS_SERVICE,
+ DEFAULT_ADVERTISING_OPTION,
+ TEST_CLIENT_UID_1
+ ) }
- postSync { advertiser.addOrUpdateService(CASE_INSENSITIVE_TEST_SERVICE_ID,
- ALL_NETWORKS_SERVICE_2, DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
+ postSync { advertiser.addOrUpdateService(
+ CASE_INSENSITIVE_TEST_SERVICE_ID,
+ ALL_NETWORKS_SERVICE_2,
+ DEFAULT_ADVERTISING_OPTION,
+ TEST_CLIENT_UID_1
+ ) }
// Callbacks for matching network and all networks both get the socket
postSync {
@@ -465,7 +614,9 @@
}
val expectedRenamed = NsdServiceInfo(
- "${ALL_NETWORKS_SERVICE.serviceName} (2)", ALL_NETWORKS_SERVICE.serviceType).apply {
+ "${ALL_NETWORKS_SERVICE.serviceName} (2)",
+ ALL_NETWORKS_SERVICE.serviceType
+ ).apply {
port = ALL_NETWORKS_SERVICE.port
hostAddresses = ALL_NETWORKS_SERVICE.hostAddresses
network = ALL_NETWORKS_SERVICE.network
@@ -473,14 +624,16 @@
val expectedLongRenamed = NsdServiceInfo(
"${LONG_ALL_NETWORKS_SERVICE.serviceName.dropLast(4)} (2)",
- LONG_ALL_NETWORKS_SERVICE.serviceType).apply {
+ LONG_ALL_NETWORKS_SERVICE.serviceType
+ ).apply {
port = LONG_ALL_NETWORKS_SERVICE.port
hostAddresses = LONG_ALL_NETWORKS_SERVICE.hostAddresses
network = LONG_ALL_NETWORKS_SERVICE.network
}
val expectedCaseInsensitiveRenamed = NsdServiceInfo(
- "${ALL_NETWORKS_SERVICE_2.serviceName} (3)", ALL_NETWORKS_SERVICE_2.serviceType
+ "${ALL_NETWORKS_SERVICE_2.serviceName} (3)",
+ ALL_NETWORKS_SERVICE_2.serviceType
).apply {
port = ALL_NETWORKS_SERVICE_2.port
hostAddresses = ALL_NETWORKS_SERVICE_2.hostAddresses
@@ -488,30 +641,58 @@
}
val intAdvCbCaptor = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
- verify(mockDeps).makeAdvertiser(eq(mockSocket1), eq(listOf(TEST_LINKADDR)),
- eq(thread.looper), any(), intAdvCbCaptor.capture(), eq(TEST_HOSTNAME), any(), any()
+ verify(mockDeps).makeAdvertiser(
+ eq(mockSocket1),
+ eq(listOf(TEST_LINKADDR)),
+ eq(thread.looper),
+ any(),
+ intAdvCbCaptor.capture(),
+ eq(TEST_HOSTNAME),
+ any(),
+ any()
)
- verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1),
- argThat { it.matches(SERVICE_1) }, any())
- verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_2),
- argThat { it.matches(expectedRenamed) }, any())
- verify(mockInterfaceAdvertiser1).addService(eq(LONG_SERVICE_ID_1),
- argThat { it.matches(LONG_SERVICE_1) }, any())
- verify(mockInterfaceAdvertiser1).addService(eq(LONG_SERVICE_ID_2),
- argThat { it.matches(expectedLongRenamed) }, any())
- verify(mockInterfaceAdvertiser1).addService(eq(CASE_INSENSITIVE_TEST_SERVICE_ID),
- argThat { it.matches(expectedCaseInsensitiveRenamed) }, any())
+ verify(mockInterfaceAdvertiser1).addService(
+ eq(SERVICE_ID_1),
+ argThat { it.matches(SERVICE_1) },
+ any()
+ )
+ verify(mockInterfaceAdvertiser1).addService(
+ eq(SERVICE_ID_2),
+ argThat { it.matches(expectedRenamed) },
+ any()
+ )
+ verify(mockInterfaceAdvertiser1).addService(
+ eq(LONG_SERVICE_ID_1),
+ argThat { it.matches(LONG_SERVICE_1) },
+ any()
+ )
+ verify(mockInterfaceAdvertiser1).addService(
+ eq(LONG_SERVICE_ID_2),
+ argThat { it.matches(expectedLongRenamed) },
+ any()
+ )
+ verify(mockInterfaceAdvertiser1).addService(
+ eq(CASE_INSENSITIVE_TEST_SERVICE_ID),
+ argThat { it.matches(expectedCaseInsensitiveRenamed) },
+ any()
+ )
doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
postSync { intAdvCbCaptor.value.onServiceProbingSucceeded(
- mockInterfaceAdvertiser1, SERVICE_ID_1) }
+ mockInterfaceAdvertiser1,
+ SERVICE_ID_1
+ ) }
verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1), argThat { it.matches(SERVICE_1) })
doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_2)
postSync { intAdvCbCaptor.value.onServiceProbingSucceeded(
- mockInterfaceAdvertiser1, SERVICE_ID_2) }
- verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_2),
- argThat { it.matches(expectedRenamed) })
+ mockInterfaceAdvertiser1,
+ SERVICE_ID_2
+ ) }
+ verify(cb).onRegisterServiceSucceeded(
+ eq(SERVICE_ID_2),
+ argThat { it.matches(expectedRenamed) }
+ )
postSync { oneNetSocketCb.onInterfaceDestroyed(TEST_SOCKETKEY_1, mockSocket1) }
postSync { allNetSocketCb.onInterfaceDestroyed(TEST_SOCKETKEY_1, mockSocket1) }
@@ -523,10 +704,21 @@
@Test
fun testAddOrUpdateService_Updates() {
val advertiser =
- MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags,
- context)
- postSync { advertiser.addOrUpdateService(SERVICE_ID_1, ALL_NETWORKS_SERVICE,
- DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
+ MdnsAdvertiser(
+ thread.looper,
+ socketProvider,
+ cb,
+ mockDeps,
+ sharedlog,
+ flags,
+ context
+ )
+ postSync { advertiser.addOrUpdateService(
+ SERVICE_ID_1,
+ ALL_NETWORKS_SERVICE,
+ DEFAULT_ADVERTISING_OPTION,
+ TEST_CLIENT_UID_1
+ ) }
val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(null), socketCbCaptor.capture())
@@ -534,41 +726,70 @@
val socketCb = socketCbCaptor.value
postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_1, mockSocket1, listOf(TEST_LINKADDR)) }
- verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1),
- argThat { it.matches(ALL_NETWORKS_SERVICE) }, any())
+ verify(mockInterfaceAdvertiser1).addService(
+ eq(SERVICE_ID_1),
+ argThat { it.matches(ALL_NETWORKS_SERVICE) },
+ any()
+ )
val updateOptions = MdnsAdvertisingOptions.newBuilder().setIsOnlyUpdate(true).build()
// Update with serviceId that is not registered yet should fail
- postSync { advertiser.addOrUpdateService(SERVICE_ID_2, ALL_NETWORKS_SERVICE_SUBTYPE,
- updateOptions, TEST_CLIENT_UID_1) }
+ postSync { advertiser.addOrUpdateService(
+ SERVICE_ID_2,
+ ALL_NETWORKS_SERVICE_SUBTYPE,
+ updateOptions,
+ TEST_CLIENT_UID_1
+ ) }
verify(cb).onRegisterServiceFailed(SERVICE_ID_2, NsdManager.FAILURE_INTERNAL_ERROR)
// Update service with different NsdServiceInfo should fail
- postSync { advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1_SUBTYPE, updateOptions,
- TEST_CLIENT_UID_1) }
+ postSync { advertiser.addOrUpdateService(
+ SERVICE_ID_1,
+ SERVICE_1_SUBTYPE,
+ updateOptions,
+ TEST_CLIENT_UID_1
+ ) }
verify(cb).onRegisterServiceFailed(SERVICE_ID_1, NsdManager.FAILURE_INTERNAL_ERROR)
// Update service with same NsdServiceInfo but different subType should succeed
- postSync { advertiser.addOrUpdateService(SERVICE_ID_1, ALL_NETWORKS_SERVICE_SUBTYPE,
- updateOptions, TEST_CLIENT_UID_1) }
+ postSync { advertiser.addOrUpdateService(
+ SERVICE_ID_1,
+ ALL_NETWORKS_SERVICE_SUBTYPE,
+ updateOptions,
+ TEST_CLIENT_UID_1
+ ) }
verify(mockInterfaceAdvertiser1).updateService(eq(SERVICE_ID_1), eq(setOf(TEST_SUBTYPE)))
// Newly created MdnsInterfaceAdvertiser will get addService() call.
postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_2, mockSocket2, listOf(TEST_LINKADDR2)) }
- verify(mockInterfaceAdvertiser2).addService(eq(SERVICE_ID_1),
- argThat { it.matches(ALL_NETWORKS_SERVICE_SUBTYPE) }, any())
+ verify(mockInterfaceAdvertiser2).addService(
+ eq(SERVICE_ID_1),
+ argThat { it.matches(ALL_NETWORKS_SERVICE_SUBTYPE) },
+ any()
+ )
}
@Test
fun testAddOrUpdateService_customTtl_registeredSuccess() {
val advertiser = MdnsAdvertiser(
- thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
+ thread.looper,
+ socketProvider,
+ cb,
+ mockDeps,
+ sharedlog,
+ flags,
+ context
+ )
val updateOptions =
MdnsAdvertisingOptions.newBuilder().setTtl(Duration.ofSeconds(30)).build()
- postSync { advertiser.addOrUpdateService(SERVICE_ID_1, ALL_NETWORKS_SERVICE,
- updateOptions, TEST_CLIENT_UID_1) }
+ postSync { advertiser.addOrUpdateService(
+ SERVICE_ID_1,
+ ALL_NETWORKS_SERVICE,
+ updateOptions,
+ TEST_CLIENT_UID_1
+ ) }
val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(null), socketCbCaptor.capture())
@@ -582,8 +803,12 @@
val advertiser =
MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
verify(mockDeps, times(1)).generateHostname(anyBoolean())
- postSync { advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1,
- DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
+ postSync { advertiser.addOrUpdateService(
+ SERVICE_ID_1,
+ SERVICE_1,
+ DEFAULT_ADVERTISING_OPTION,
+ TEST_CLIENT_UID_1
+ ) }
postSync { advertiser.removeService(SERVICE_ID_1) }
verify(mockDeps, times(2)).generateHostname(anyBoolean())
}
@@ -593,8 +818,12 @@
val flags = MdnsFeatureFlags.newBuilder().setIsShortHostnamesEnabled(shortHostname).build()
val advertiser =
MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
- postSync { advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1,
- DEFAULT_ADVERTISING_OPTION, TEST_CLIENT_UID_1) }
+ postSync { advertiser.addOrUpdateService(
+ SERVICE_ID_1,
+ SERVICE_1,
+ DEFAULT_ADVERTISING_OPTION,
+ TEST_CLIENT_UID_1
+ ) }
val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(TEST_NETWORK_1), socketCbCaptor.capture())
@@ -621,8 +850,10 @@
val hostname = doHostnameGenerationTest(shortHostname = true)
// Short hostnames are [8 uppercase letters or digits].local
assertEquals(2, hostname.size)
- assertTrue(Regex("Android_[A-Z0-9]{8}").matches(hostname[0]),
- "Unexpected hostname: ${hostname.contentToString()}")
+ assertTrue(
+ Regex("Android_[A-Z0-9]{8}").matches(hostname[0]),
+ "Unexpected hostname: ${hostname.contentToString()}"
+ )
assertEquals("local", hostname[1])
}
@@ -631,8 +862,10 @@
val hostname = doHostnameGenerationTest(shortHostname = false)
// Long hostnames are Android_[32 lowercase hex characters].local
assertEquals(2, hostname.size)
- assertTrue(Regex("Android_[a-f0-9]{32}").matches(hostname[0]),
- "Unexpected hostname: ${hostname.contentToString()}")
+ assertTrue(
+ Regex("Android_[a-f0-9]{32}").matches(hostname[0]),
+ "Unexpected hostname: ${hostname.contentToString()}"
+ )
assertEquals("local", hostname[1])
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
index ab2fb99..71a3274 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
@@ -442,14 +442,12 @@
// Verify the checkAndRunOnHandlerThread method
final CompletableFuture<Boolean> future1 = new CompletableFuture<>();
executor.checkAndRunOnHandlerThread(()-> future1.complete(true));
- assertTrue(future1.isDone());
assertTrue(future1.get(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS));
// Verify the execute method
final CompletableFuture<Boolean> future2 = new CompletableFuture<>();
executor.execute(()-> future2.complete(true));
testableLooper.processAllMessages();
- assertTrue(future2.isDone());
assertTrue(future2.get(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS));
// Verify the executeDelayed method
@@ -469,7 +467,6 @@
// The function should be executed.
testableLooper.moveTimeForward(500L);
testableLooper.processAllMessages();
- assertTrue(future3.isDone());
assertTrue(future3.get(500L, TimeUnit.MILLISECONDS));
} finally {
testableLooper.destroy();
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSBpfNetMapsTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSBpfNetMapsTest.kt
index 8155fd0..06cb7ee 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSBpfNetMapsTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSBpfNetMapsTest.kt
@@ -21,7 +21,10 @@
import android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED
import android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED
import android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED
+import android.net.InetAddresses
+import android.net.LinkProperties
import android.os.Build
+import android.os.Build.VERSION_CODES
import androidx.test.filters.SmallTest
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
@@ -33,11 +36,32 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+internal val LOCAL_DNS = InetAddresses.parseNumericAddress("224.0.1.2")
+internal val NON_LOCAL_DNS = InetAddresses.parseNumericAddress("76.76.75.75")
+
+private const val IFNAME_1 = "wlan1"
+private const val IFNAME_2 = "wlan2"
+private const val PORT_53 = 53
+private const val PROTOCOL_TCP = 6
+private const val PROTOCOL_UDP = 17
+
+private val lpWithNoLocalDns = LinkProperties().apply {
+ addDnsServer(NON_LOCAL_DNS)
+ interfaceName = IFNAME_1
+}
+
+private val lpWithLocalDns = LinkProperties().apply {
+ addDnsServer(LOCAL_DNS)
+ interfaceName = IFNAME_2
+}
+
@DevSdkIgnoreRunner.MonitorThreadLeak
@RunWith(DevSdkIgnoreRunner::class)
@SmallTest
@@ -69,6 +93,81 @@
}
}
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @Test
+ fun testLocalPrefixesUpdatedInBpfMap() {
+ // Connect Wi-Fi network with non-local dns.
+ val wifiAgent = Agent(nc = defaultNc(), lp = lpWithNoLocalDns)
+ wifiAgent.connect()
+
+ // Verify that block rule is added to BpfMap for local prefixes.
+ verify(bpfNetMaps, atLeastOnce()).addLocalNetAccess(any(), eq(IFNAME_1),
+ any(), eq(0), eq(0), eq(false))
+
+ wifiAgent.disconnect()
+ val cellAgent = Agent(nc = defaultNc(), lp = lpWithLocalDns)
+ cellAgent.connect()
+
+ // Verify that block rule is removed from BpfMap for local prefixes.
+ verify(bpfNetMaps, atLeastOnce()).removeLocalNetAccess(any(), eq(IFNAME_1),
+ any(), eq(0), eq(0))
+
+ cellAgent.disconnect()
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @Test
+ fun testLocalDnsNotUpdatedInBpfMap() {
+ // Connect Wi-Fi network with non-local dns.
+ val wifiAgent = Agent(nc = defaultNc(), lp = lpWithNoLocalDns)
+ wifiAgent.connect()
+
+ // Verify that No allow rule is added to BpfMap since there is no local dns.
+ verify(bpfNetMaps, never()).addLocalNetAccess(any(), any(), any(), any(), any(),
+ eq(true))
+
+ wifiAgent.disconnect()
+ val cellAgent = Agent(nc = defaultNc(), lp = lpWithLocalDns)
+ cellAgent.connect()
+
+ // Verify that No allow rule from port 53 is removed on network change
+ // because no dns was added
+ verify(bpfNetMaps, never()).removeLocalNetAccess(eq(192), eq(IFNAME_1),
+ eq(NON_LOCAL_DNS), any(), eq(PORT_53))
+
+ cellAgent.disconnect()
+ }
+
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @Test
+ fun testLocalDnsUpdatedInBpfMap() {
+ // Connect Wi-Fi network with one local Dns.
+ val wifiAgent = Agent(nc = defaultNc(), lp = lpWithLocalDns)
+ wifiAgent.connect()
+
+ // Verify that allow rule is added to BpfMap for local dns at port 53,
+ // for TCP(=6) protocol
+ verify(bpfNetMaps, atLeastOnce()).addLocalNetAccess(eq(192), eq(IFNAME_2),
+ eq(LOCAL_DNS), eq(PROTOCOL_TCP), eq(PORT_53), eq(true))
+ // And for UDP(=17) protocol
+ verify(bpfNetMaps, atLeastOnce()).addLocalNetAccess(eq(192), eq(IFNAME_2),
+ eq(LOCAL_DNS), eq(PROTOCOL_UDP), eq(PORT_53), eq(true))
+
+ wifiAgent.disconnect()
+ val cellAgent = Agent(nc = defaultNc(), lp = lpWithNoLocalDns)
+ cellAgent.connect()
+
+ // Verify that allow rule is removed for local dns on network change,
+ // for TCP(=6) protocol
+ verify(bpfNetMaps, atLeastOnce()).removeLocalNetAccess(eq(192), eq(IFNAME_2),
+ eq(LOCAL_DNS), eq(PROTOCOL_TCP), eq(PORT_53))
+ // And for UDP(=17) protocol
+ verify(bpfNetMaps, atLeastOnce()).removeLocalNetAccess(eq(192), eq(IFNAME_2),
+ eq(LOCAL_DNS), eq(PROTOCOL_UDP), eq(PORT_53))
+
+ cellAgent.disconnect()
+ }
+
private fun mockDataSaverStatus(status: Int) {
doReturn(status).`when`(context.networkPolicyManager).getRestrictBackgroundStatus(anyInt())
// While the production code dispatches the intent on the handler thread,
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSLocalNetworkProtectionTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSLocalNetworkProtectionTest.kt
new file mode 100644
index 0000000..73e7515
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSLocalNetworkProtectionTest.kt
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2023 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
+
+import android.net.InetAddresses
+import android.net.IpPrefix
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
+import android.net.NetworkCapabilities.TRANSPORT_VPN
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.Lost
+import com.android.testutils.TestableNetworkCallback
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+private const val LONG_TIMEOUT_MS = 5_000
+private const val PREFIX_LENGTH_IPV4 = 32 + 96
+private const val PREFIX_LENGTH_IPV6 = 32
+private const val WIFI_IFNAME = "wlan0"
+private const val WIFI_IFNAME_2 = "wlan1"
+
+private val wifiNc = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .build()
+
+private fun lp(iface: String, vararg linkAddresses: LinkAddress) = LinkProperties().apply {
+ interfaceName = iface
+ for (linkAddress in linkAddresses) {
+ addLinkAddress(linkAddress)
+ }
+}
+
+private fun nr(transport: Int) = NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(transport).apply {
+ if (transport != TRANSPORT_VPN) {
+ addCapability(NET_CAPABILITY_NOT_VPN)
+ }
+ }.build()
+
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+class CSLocalNetworkProtectionTest : CSTest() {
+ private val LOCAL_IPV6_IP_ADDRESS_PREFIX = IpPrefix("fe80::1cf1:35ff:fe8c:db87/64")
+ private val LOCAL_IPV6_LINK_ADDRESS = LinkAddress(
+ LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress(),
+ LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()
+ )
+
+ private val LOCAL_IPV4_IP_ADDRESS_PREFIX_1 = IpPrefix("10.0.0.184/24")
+ private val LOCAL_IPV4_LINK_ADDRRESS_1 =
+ LinkAddress(
+ LOCAL_IPV4_IP_ADDRESS_PREFIX_1.getAddress(),
+ LOCAL_IPV4_IP_ADDRESS_PREFIX_1.getPrefixLength()
+ )
+
+ private val LOCAL_IPV4_IP_ADDRESS_PREFIX_2 = IpPrefix("10.0.255.184/24")
+ private val LOCAL_IPV4_LINK_ADDRRESS_2 =
+ LinkAddress(
+ LOCAL_IPV4_IP_ADDRESS_PREFIX_2.getAddress(),
+ LOCAL_IPV4_IP_ADDRESS_PREFIX_2.getPrefixLength()
+ )
+
+ @Test
+ fun testNetworkWithIPv6LocalAddress_AddressAddedToBpfMap() {
+ val nr = nr(TRANSPORT_WIFI)
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(nr, cb)
+
+ // Connecting to network with IPv6 local address in LinkProperties
+ val wifiLp = lp(WIFI_IFNAME, LOCAL_IPV6_LINK_ADDRESS)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+
+ // Multicast and Broadcast address should always be populated in local_net_access map
+ verifyPopulationOfMulticastAndBroadcastAddress()
+ // Verifying IPv6 address should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()),
+ eq(WIFI_IFNAME),
+ eq(LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress()),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+ }
+
+ @Test
+ fun testNetworkWithIPv4LocalAddress_AddressAddedToBpfMap() {
+ val nr = nr(TRANSPORT_WIFI)
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(nr, cb)
+
+ val wifiLp = lp(WIFI_IFNAME, LOCAL_IPV4_LINK_ADDRRESS_1)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+
+ // Multicast and Broadcast address should always be populated in local_net_access map
+ verifyPopulationOfMulticastAndBroadcastAddress()
+
+ // Verifying IPv4 matching prefix(10.0.0.0/8) should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV4 + 8),
+ eq(WIFI_IFNAME),
+ eq(InetAddresses.parseNumericAddress("10.0.0.0")),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+ }
+
+ @Test
+ fun testChangeLinkPropertiesWithDifferentLinkAddresses_AddressReplacedInBpfMap() {
+ val nr = nr(TRANSPORT_WIFI)
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(nr, cb)
+
+ val wifiLp = lp(WIFI_IFNAME, LOCAL_IPV6_LINK_ADDRESS)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+
+ // Multicast and Broadcast address should always be populated in local_net_access map
+ verifyPopulationOfMulticastAndBroadcastAddress()
+ // Verifying IPv6 address should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()),
+ eq(WIFI_IFNAME),
+ eq(LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress()),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+
+ // Updating Link Property from IPv6 in Link Address to IPv4 in Link Address
+ val wifiLp2 = lp(WIFI_IFNAME, LOCAL_IPV4_LINK_ADDRRESS_1)
+ wifiAgent.sendLinkProperties(wifiLp2)
+ cb.expect<LinkPropertiesChanged>(wifiAgent.network)
+
+ // Verifying IPv4 matching prefix(10.0.0.0/8) should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV4 + 8),
+ eq(WIFI_IFNAME),
+ eq(InetAddresses.parseNumericAddress("10.0.0.0")),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+ // Verifying IPv6 address should be removed from local_net_access map
+ verify(bpfNetMaps).removeLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()),
+ eq(WIFI_IFNAME),
+ eq(LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress()),
+ eq(0),
+ eq(0)
+ )
+ }
+
+ @Test
+ fun testChangeLinkPropertiesWithLinkAddressesInSameRange_AddressIntactInBpfMap() {
+ val nr = nr(TRANSPORT_WIFI)
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(nr, cb)
+
+ val wifiLp = lp(WIFI_IFNAME, LOCAL_IPV4_LINK_ADDRRESS_1)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+
+ // Multicast and Broadcast address should always be populated in local_net_access map
+ verifyPopulationOfMulticastAndBroadcastAddress()
+ // Verifying IPv4 matching prefix(10.0.0.0/8) should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV4 + 8),
+ eq(WIFI_IFNAME),
+ eq(LOCAL_IPV4_IP_ADDRESS_PREFIX_1.getAddress()),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+
+ // Updating Link Property from one IPv4 to another IPv4 within same range(10.0.0.0/8)
+ val wifiLp2 = lp(WIFI_IFNAME, LOCAL_IPV4_LINK_ADDRRESS_2)
+ wifiAgent.sendLinkProperties(wifiLp2)
+ cb.expect<LinkPropertiesChanged>(wifiAgent.network)
+
+ // As both addresses below to same range, so no address should be removed from the map.
+ verify(bpfNetMaps, never()).removeLocalNetAccess(any(), any(), any(), any(), any())
+ }
+
+ @Test
+ fun testChangeLinkPropertiesWithDifferentInterface_AddressReplacedInBpfMap() {
+ val nr = nr(TRANSPORT_WIFI)
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(nr, cb)
+
+ val wifiLp = lp(WIFI_IFNAME, LOCAL_IPV6_LINK_ADDRESS)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+
+ // Multicast and Broadcast address should always be populated in local_net_access map
+ verifyPopulationOfMulticastAndBroadcastAddress()
+ // Verifying IPv6 address should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()),
+ eq(WIFI_IFNAME),
+ eq(LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress()),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+
+ // Updating Link Property by changing interface name which has IPv4 instead of IPv6
+ val wifiLp2 = lp(WIFI_IFNAME_2, LOCAL_IPV4_LINK_ADDRRESS_1)
+ wifiAgent.sendLinkProperties(wifiLp2)
+ cb.expect<LinkPropertiesChanged>(wifiAgent.network)
+
+ // Multicast and Broadcast address should be populated in local_net_access map for
+ // new interface
+ verifyPopulationOfMulticastAndBroadcastAddress(WIFI_IFNAME_2)
+ // Verifying IPv4 matching prefix(10.0.0.0/8) should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV4 + 8),
+ eq(WIFI_IFNAME_2),
+ eq(InetAddresses.parseNumericAddress("10.0.0.0")),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+ // Multicast and Broadcast address should be removed in local_net_access map for
+ // old interface
+ verifyRemovalOfMulticastAndBroadcastAddress()
+ // Verifying IPv6 address should be removed from local_net_access map
+ verify(bpfNetMaps).removeLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()),
+ eq(WIFI_IFNAME),
+ eq(LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress()),
+ eq(0),
+ eq(0)
+ )
+ }
+
+ @Test
+ fun testAddingAnotherNetwork_AllAddressesAddedInBpfMap() {
+ val nr = nr(TRANSPORT_WIFI)
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(nr, cb)
+
+ val wifiLp = lp(WIFI_IFNAME, LOCAL_IPV6_LINK_ADDRESS)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+
+ // Multicast and Broadcast address should always be populated in local_net_access map
+ verifyPopulationOfMulticastAndBroadcastAddress()
+ // Verifying IPv6 address should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()),
+ eq(WIFI_IFNAME),
+ eq(LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress()),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+
+ // Adding another network with LinkProperty having IPv4 in LinkAddress
+ val wifiLp2 = lp(WIFI_IFNAME_2, LOCAL_IPV4_LINK_ADDRRESS_1)
+ val wifiAgent2 = Agent(nc = wifiNc, lp = wifiLp2)
+ wifiAgent2.connect()
+
+ // Multicast and Broadcast address should always be populated in local_net_access map
+ verifyPopulationOfMulticastAndBroadcastAddress(WIFI_IFNAME_2)
+ // Verifying IPv4 matching prefix(10.0.0.0/8) should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV4 + 8),
+ eq(WIFI_IFNAME_2),
+ eq(InetAddresses.parseNumericAddress("10.0.0.0")),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+ // Verifying nothing should be removed from local_net_access map
+ verify(bpfNetMaps, never()).removeLocalNetAccess(any(), any(), any(), any(), any())
+ }
+
+ @Test
+ fun testDestroyingNetwork_AddressesRemovedFromBpfMap() {
+ val nr = nr(TRANSPORT_WIFI)
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(nr, cb)
+
+ val wifiLp = lp(WIFI_IFNAME, LOCAL_IPV6_LINK_ADDRESS)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+
+ // Multicast and Broadcast address should always be populated in local_net_access map
+ verifyPopulationOfMulticastAndBroadcastAddress()
+ // Verifying IPv6 address should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq( PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()),
+ eq(WIFI_IFNAME),
+ eq(LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress()),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+
+ // Unregistering the network
+ wifiAgent.unregisterAfterReplacement(LONG_TIMEOUT_MS)
+ cb.expect<Lost>(wifiAgent.network)
+
+ // Multicast and Broadcast address should be removed in local_net_access map for
+ // old interface
+ verifyRemovalOfMulticastAndBroadcastAddress()
+ // Verifying IPv6 address should be removed from local_net_access map
+ verify(bpfNetMaps).removeLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()),
+ eq(WIFI_IFNAME),
+ eq(LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress()),
+ eq(0),
+ eq(0)
+ )
+ }
+
+ // Verify if multicast and broadcast addresses have been added using addLocalNetAccess
+ fun verifyPopulationOfMulticastAndBroadcastAddress(
+ interfaceName: String = WIFI_IFNAME
+ ) {
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV4 + 4),
+ eq(interfaceName),
+ eq(InetAddresses.parseNumericAddress("224.0.0.0")),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV6 + 8),
+ eq(interfaceName),
+ eq(InetAddresses.parseNumericAddress("ff00::")),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV4 + 32),
+ eq(interfaceName),
+ eq(InetAddresses.parseNumericAddress("255.255.255.255")),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+ }
+
+ // Verify if multicast and broadcast addresses have been removed using removeLocalNetAccess
+ fun verifyRemovalOfMulticastAndBroadcastAddress(
+ interfaceName: String = WIFI_IFNAME
+ ) {
+ verify(bpfNetMaps).removeLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV4 + 4),
+ eq(interfaceName),
+ eq(InetAddresses.parseNumericAddress("224.0.0.0")),
+ eq(0),
+ eq(0)
+ )
+ verify(bpfNetMaps).removeLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV6 + 8),
+ eq(interfaceName),
+ eq(InetAddresses.parseNumericAddress("ff00::")),
+ eq(0),
+ eq(0)
+ )
+ verify(bpfNetMaps).removeLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV4 + 32),
+ eq(interfaceName),
+ eq(InetAddresses.parseNumericAddress("255.255.255.255")),
+ eq(0),
+ eq(0)
+ )
+ }
+}