Merge "Adding stacked link addresses to local_net_access map" into main
diff --git a/bpf/tests/mts/bpf_existence_test.cpp b/bpf/tests/mts/bpf_existence_test.cpp
index 2cfa546..0ecda3d 100644
--- a/bpf/tests/mts/bpf_existence_test.cpp
+++ b/bpf/tests/mts/bpf_existence_test.cpp
@@ -21,7 +21,6 @@
#include <string>
#include <android-base/properties.h>
-#include <android-modules-utils/sdk_level.h>
#include <android/api-level.h>
#include <bpf/BpfUtils.h>
@@ -32,11 +31,6 @@
using std::string;
using android::bpf::isAtLeastKernelVersion;
-using android::modules::sdklevel::IsAtLeastR;
-using android::modules::sdklevel::IsAtLeastS;
-using android::modules::sdklevel::IsAtLeastT;
-using android::modules::sdklevel::IsAtLeastU;
-using android::modules::sdklevel::IsAtLeastV;
#define PLATFORM "/sys/fs/bpf/"
#define TETHERING "/sys/fs/bpf/tethering/"
@@ -48,10 +42,15 @@
class BpfExistenceTest : public ::testing::Test {
};
-//ToDo: replace isAtLeast25Q2 with IsAtLeastB once sdk_level have been upgraded to 36 on aosp/main
const bool unreleased = (android::base::GetProperty("ro.build.version.codename", "REL") != "REL");
-const int api_level = unreleased ? __ANDROID_API_FUTURE__ : android_get_device_api_level();
-const bool isAtLeast25Q2 = (api_level > __ANDROID_API_V__);
+const int api_level = unreleased ? 10000 : android_get_device_api_level();
+const bool isAtLeastR = (api_level >= 30);
+const bool isAtLeastS = (api_level >= 31);
+// Sv2 is 32
+const bool isAtLeastT = (api_level >= 33);
+const bool isAtLeastU = (api_level >= 34);
+const bool isAtLeastV = (api_level >= 35);
+const bool isAtLeast25Q2 = (api_level >= 36);
// Part of Android R platform (for 4.9+), but mainlined in S
static const set<string> PLATFORM_ONLY_IN_R = {
@@ -194,33 +193,33 @@
// and for the presence of mainline stuff.
// Note: Q is no longer supported by mainline
- ASSERT_TRUE(IsAtLeastR());
+ ASSERT_TRUE(isAtLeastR);
// R can potentially run on pre-4.9 kernel non-eBPF capable devices.
- DO_EXPECT(IsAtLeastR() && !IsAtLeastS() && isAtLeastKernelVersion(4, 9, 0), PLATFORM_ONLY_IN_R);
+ DO_EXPECT(isAtLeastR && !isAtLeastS && isAtLeastKernelVersion(4, 9, 0), PLATFORM_ONLY_IN_R);
// S requires Linux Kernel 4.9+ and thus requires eBPF support.
- if (IsAtLeastS()) ASSERT_TRUE(isAtLeastKernelVersion(4, 9, 0));
- DO_EXPECT(IsAtLeastS(), MAINLINE_FOR_S_PLUS);
+ if (isAtLeastS) ASSERT_TRUE(isAtLeastKernelVersion(4, 9, 0));
+ DO_EXPECT(isAtLeastS, MAINLINE_FOR_S_PLUS);
// Nothing added or removed in SCv2.
// T still only requires Linux Kernel 4.9+.
- DO_EXPECT(IsAtLeastT(), MAINLINE_FOR_T_PLUS);
- DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(4, 14, 0), MAINLINE_FOR_T_4_14_PLUS);
- DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(4, 19, 0), MAINLINE_FOR_T_4_19_PLUS);
- DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(5, 10, 0), MAINLINE_FOR_T_5_10_PLUS);
- DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(5, 15, 0), MAINLINE_FOR_T_5_15_PLUS);
+ DO_EXPECT(isAtLeastT, MAINLINE_FOR_T_PLUS);
+ DO_EXPECT(isAtLeastT && isAtLeastKernelVersion(4, 14, 0), MAINLINE_FOR_T_4_14_PLUS);
+ DO_EXPECT(isAtLeastT && isAtLeastKernelVersion(4, 19, 0), MAINLINE_FOR_T_4_19_PLUS);
+ DO_EXPECT(isAtLeastT && isAtLeastKernelVersion(5, 10, 0), MAINLINE_FOR_T_5_10_PLUS);
+ DO_EXPECT(isAtLeastT && isAtLeastKernelVersion(5, 15, 0), MAINLINE_FOR_T_5_15_PLUS);
// U requires Linux Kernel 4.14+, but nothing (as yet) added or removed in U.
- if (IsAtLeastU()) ASSERT_TRUE(isAtLeastKernelVersion(4, 14, 0));
- DO_EXPECT(IsAtLeastU(), MAINLINE_FOR_U_PLUS);
- DO_EXPECT(IsAtLeastU() && isAtLeastKernelVersion(5, 10, 0), MAINLINE_FOR_U_5_10_PLUS);
+ if (isAtLeastU) ASSERT_TRUE(isAtLeastKernelVersion(4, 14, 0));
+ DO_EXPECT(isAtLeastU, MAINLINE_FOR_U_PLUS);
+ DO_EXPECT(isAtLeastU && isAtLeastKernelVersion(5, 10, 0), MAINLINE_FOR_U_5_10_PLUS);
// V requires Linux Kernel 4.19+, but nothing (as yet) added or removed in V.
- if (IsAtLeastV()) ASSERT_TRUE(isAtLeastKernelVersion(4, 19, 0));
- DO_EXPECT(IsAtLeastV(), MAINLINE_FOR_V_PLUS);
- DO_EXPECT(IsAtLeastV() && isAtLeastKernelVersion(5, 4, 0), MAINLINE_FOR_V_5_4_PLUS);
+ if (isAtLeastV) ASSERT_TRUE(isAtLeastKernelVersion(4, 19, 0));
+ DO_EXPECT(isAtLeastV, MAINLINE_FOR_V_PLUS);
+ DO_EXPECT(isAtLeastV && isAtLeastKernelVersion(5, 4, 0), MAINLINE_FOR_V_5_4_PLUS);
if (isAtLeast25Q2) ASSERT_TRUE(isAtLeastKernelVersion(5, 4, 0));
DO_EXPECT(isAtLeast25Q2, MAINLINE_FOR_25Q2_PLUS);
diff --git a/framework/Android.bp b/framework/Android.bp
index d6ee759..f66bc60 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -162,7 +162,6 @@
srcs: [":httpclient_api_sources"],
static_libs: [
"com.android.net.http.flags-aconfig-java",
- "modules-utils-expresslog",
],
libs: [
"androidx.annotation_annotation",
diff --git a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
index 0536263..317854b 100644
--- a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
+++ b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
@@ -143,7 +143,7 @@
* @hide
*/
@ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT)
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT)
public static final long RESTRICT_LOCAL_NETWORK = 365139289L;
private ConnectivityCompatChanges() {
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index a8e3203..27f039f 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -191,7 +191,6 @@
import com.android.networkstack.apishim.BroadcastOptionsShimImpl;
import com.android.networkstack.apishim.ConstantsShim;
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
-import com.android.server.BpfNetMaps;
import com.android.server.connectivity.ConnectivityResources;
import java.io.File;
@@ -726,11 +725,6 @@
mTrafficStatsUidCache = null;
}
- // TODO: Remove bpfNetMaps creation and always start SkDestroyListener
- // Following code is for the experiment to verify the SkDestroyListener refactoring. Based
- // on the experiment flag, BpfNetMaps starts C SkDestroyListener (existing code) or
- // NetworkStatsService starts Java SkDestroyListener (new code).
- final BpfNetMaps bpfNetMaps = mDeps.makeBpfNetMaps(mContext);
mSkDestroyListener = mDeps.makeSkDestroyListener(mCookieTagMap, mHandler);
mHandler.post(mSkDestroyListener::start);
}
@@ -952,11 +946,6 @@
return Build.isDebuggable();
}
- /** Create a new BpfNetMaps. */
- public BpfNetMaps makeBpfNetMaps(Context ctx) {
- return new BpfNetMaps(ctx);
- }
-
/** Create a new SkDestroyListener. */
public SkDestroyListener makeSkDestroyListener(
IBpfMap<CookieTagMapKey, CookieTagMapValue> cookieTagMap, Handler handler) {
diff --git a/service/libconnectivity/include/connectivity_native.h b/service/libconnectivity/include/connectivity_native.h
index f4676a9..f264b68 100644
--- a/service/libconnectivity/include/connectivity_native.h
+++ b/service/libconnectivity/include/connectivity_native.h
@@ -20,12 +20,6 @@
#include <sys/cdefs.h>
#include <netinet/in.h>
-// For branches that do not yet have __ANDROID_API_U__ defined, like module
-// release branches.
-#ifndef __ANDROID_API_U__
-#define __ANDROID_API_U__ 34
-#endif
-
__BEGIN_DECLS
/**
@@ -41,7 +35,7 @@
*
* @param port Int corresponding to port number.
*/
-int AConnectivityNative_blockPortForBind(in_port_t port) __INTRODUCED_IN(__ANDROID_API_U__);
+int AConnectivityNative_blockPortForBind(in_port_t port) __INTRODUCED_IN(34);
/**
* Unblocks a port that has previously been blocked.
@@ -54,7 +48,7 @@
*
* @param port Int corresponding to port number.
*/
-int AConnectivityNative_unblockPortForBind(in_port_t port) __INTRODUCED_IN(__ANDROID_API_U__);
+int AConnectivityNative_unblockPortForBind(in_port_t port) __INTRODUCED_IN(34);
/**
* Unblocks all ports that have previously been blocked.
@@ -64,7 +58,7 @@
* - EPERM if the UID of the client doesn't have network stack permission
* - Other errors as per https://man7.org/linux/man-pages/man2/bpf.2.html
*/
-int AConnectivityNative_unblockAllPortsForBind() __INTRODUCED_IN(__ANDROID_API_U__);
+int AConnectivityNative_unblockAllPortsForBind() __INTRODUCED_IN(34);
/**
* Gets the list of ports that have been blocked.
@@ -79,7 +73,7 @@
* blocked ports, which may be larger than the ports array that was filled.
*/
int AConnectivityNative_getPortsBlockedForBind(in_port_t* _Nonnull ports, size_t* _Nonnull count)
- __INTRODUCED_IN(__ANDROID_API_U__);
+ __INTRODUCED_IN(34);
__END_DECLS
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index a9d993c..661a2f7 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -1023,6 +1023,8 @@
private final LingerMonitor mLingerMonitor;
private final SatelliteAccessController mSatelliteAccessController;
+ private final L2capNetworkProvider mL2capNetworkProvider;
+
// sequence number of NetworkRequests
private int mNextNetworkRequestId = NetworkRequest.FIRST_REQUEST_ID;
@@ -1623,6 +1625,11 @@
connectivityServiceInternalHandler);
}
+ /** Creates an L2capNetworkProvider */
+ public L2capNetworkProvider makeL2capNetworkProvider(Context context) {
+ return new L2capNetworkProvider(context);
+ }
+
/** Returns the data inactivity timeout to be used for cellular networks */
public int getDefaultCellularDataInactivityTimeout() {
return DeviceConfigUtils.getDeviceConfigPropertyInt(NAMESPACE_TETHERING_BOOT,
@@ -2094,6 +2101,8 @@
}
mIngressToVpnAddressFiltering = mDeps.isAtLeastT()
&& mDeps.isFeatureNotChickenedOut(mContext, INGRESS_TO_VPN_ADDRESS_FILTERING);
+
+ mL2capNetworkProvider = mDeps.makeL2capNetworkProvider(mContext);
}
/**
@@ -4129,6 +4138,10 @@
mCarrierPrivilegeAuthenticator.start();
}
+ if (mL2capNetworkProvider != null) {
+ mL2capNetworkProvider.start();
+ }
+
// On T+ devices, register callback for statsd to pull NETWORK_BPF_MAP_INFO atom
if (mDeps.isAtLeastT()) {
mBpfNetMaps.setPullAtomCallback(mContext);
diff --git a/service/src/com/android/server/L2capNetworkProvider.java b/service/src/com/android/server/L2capNetworkProvider.java
index d0b0603..72bd858 100644
--- a/service/src/com/android/server/L2capNetworkProvider.java
+++ b/service/src/com/android/server/L2capNetworkProvider.java
@@ -16,7 +16,7 @@
package com.android.server;
-import static android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_6LOWPAN;
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH_LE;
import static android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_ANY;
import static android.net.L2capNetworkSpecifier.PSM_ANY;
import static android.net.L2capNetworkSpecifier.ROLE_CLIENT;
@@ -30,7 +30,6 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.RES_ID_MATCH_ALL_RESERVATIONS;
import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
-import static android.content.pm.PackageManager.FEATURE_BLUETOOTH_LE;
import static android.system.OsConstants.F_GETFL;
import static android.system.OsConstants.F_SETFL;
import static android.system.OsConstants.O_NONBLOCK;
@@ -50,10 +49,8 @@
import android.net.NetworkProvider.NetworkOfferCallback;
import android.net.NetworkRequest;
import android.net.NetworkScore;
-import android.net.NetworkSpecifier;
import android.os.Handler;
import android.os.HandlerThread;
-import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.system.Os;
import android.util.ArrayMap;
@@ -95,8 +92,9 @@
private final BlanketReservationOffer mBlanketOffer;
private final Set<ReservedServerOffer> mReservedServerOffers = new ArraySet<>();
private final ClientOffer mClientOffer;
- private final BluetoothManager mBluetoothManager;
- private final boolean mIsSupported;
+ // mBluetoothManager guaranteed non-null when read on handler thread after start() is called
+ @Nullable
+ private BluetoothManager mBluetoothManager;
// Note: IFNAMSIZ is 16.
private static final String TUN_IFNAME = "l2cap-tun";
@@ -110,12 +108,7 @@
* requests that do not have a NetworkSpecifier set.
*/
private class BlanketReservationOffer implements NetworkOfferCallback {
- // Set as transport primary to ensure that the BlanketReservationOffer always outscores the
- // ReservedServerOffer, because as soon as the BlanketReservationOffer receives an
- // onNetworkUnneeded() callback, it will tear down the associated reserved offer.
- public static final NetworkScore SCORE = new NetworkScore.Builder()
- .setTransportPrimary(true)
- .build();
+ public static final NetworkScore SCORE = new NetworkScore.Builder().build();
// Note the missing NET_CAPABILITY_NOT_RESTRICTED marking the network as restricted.
public static final NetworkCapabilities CAPABILITIES;
static {
@@ -133,12 +126,7 @@
}
// TODO: consider moving this into L2capNetworkSpecifier as #isValidServerReservation().
- private boolean isValidL2capSpecifier(@Nullable NetworkSpecifier spec) {
- if (spec == null) return false;
- // If spec is not null, L2capNetworkSpecifier#canBeSatisfiedBy() guarantees the
- // specifier is of type L2capNetworkSpecifier.
- final L2capNetworkSpecifier l2capSpec = (L2capNetworkSpecifier) spec;
-
+ private boolean isValidL2capServerSpecifier(L2capNetworkSpecifier l2capSpec) {
// The ROLE_SERVER offer can be satisfied by a ROLE_ANY request.
if (l2capSpec.getRole() != ROLE_SERVER) return false;
@@ -156,9 +144,13 @@
@Override
public void onNetworkNeeded(NetworkRequest request) {
- Log.d(TAG, "New reservation request: " + request);
- if (!isValidL2capSpecifier(request.getNetworkSpecifier())) {
- Log.w(TAG, "Ignoring invalid reservation request: " + request);
+ // The NetworkSpecifier is guaranteed to be either null or an L2capNetworkSpecifier, so
+ // this cast is safe.
+ final L2capNetworkSpecifier specifier =
+ (L2capNetworkSpecifier) request.getNetworkSpecifier();
+ if (specifier == null) return;
+ if (!isValidL2capServerSpecifier(specifier)) {
+ Log.i(TAG, "Ignoring invalid reservation request: " + request);
return;
}
@@ -243,36 +235,15 @@
}
@Nullable
- private static ParcelFileDescriptor createTunInterface(String ifname) {
- final ParcelFileDescriptor fd;
- try {
- fd = ParcelFileDescriptor.adoptFd(
- ServiceConnectivityJni.createTunTap(
- true /*isTun*/, true /*hasCarrier*/, true /*setIffMulticast*/, ifname));
- ServiceConnectivityJni.bringUpInterface(ifname);
- // TODO: consider adding a parameter to createTunTap() (or the Builder that should
- // be added) to configure i/o blocking.
- final int flags = Os.fcntlInt(fd.getFileDescriptor(), F_GETFL, 0);
- Os.fcntlInt(fd.getFileDescriptor(), F_SETFL, flags & ~O_NONBLOCK);
- } catch (Exception e) {
- // Note: createTunTap currently throws an IllegalStateException on failure.
- // TODO: native functions should throw ErrnoException.
- Log.e(TAG, "Failed to create tun interface", e);
- return null;
- }
- return fd;
- }
-
- @Nullable
private L2capNetwork createL2capNetwork(BluetoothSocket socket, NetworkCapabilities caps,
L2capNetwork.ICallback cb) {
final String ifname = TUN_IFNAME + String.valueOf(sTunIndex++);
- final ParcelFileDescriptor tunFd = createTunInterface(ifname);
+ final ParcelFileDescriptor tunFd = mDeps.createTunInterface(ifname);
if (tunFd == null) {
return null;
}
- return new L2capNetwork(mHandler, mContext, mProvider, ifname, socket, tunFd, caps, cb);
+ return L2capNetwork.create(mHandler, mContext, mProvider, ifname, socket, tunFd, caps, cb);
}
private static void closeBluetoothSocket(BluetoothSocket socket) {
@@ -520,12 +491,7 @@
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;
-
+ private boolean isValidL2capClientSpecifier(L2capNetworkSpecifier l2capSpec) {
// The ROLE_CLIENT offer can be satisfied by a ROLE_ANY request.
if (l2capSpec.getRole() != ROLE_CLIENT) return false;
@@ -545,14 +511,16 @@
@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);
+ // The NetworkSpecifier is guaranteed to be either null or an L2capNetworkSpecifier, so
+ // this cast is safe.
+ final L2capNetworkSpecifier requestSpecifier =
+ (L2capNetworkSpecifier) request.getNetworkSpecifier();
+ if (requestSpecifier == null) return;
+ if (!isValidL2capClientSpecifier(requestSpecifier)) {
+ Log.i(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) {
@@ -654,17 +622,35 @@
@VisibleForTesting
public static class Dependencies {
- /** Get NetworkProvider */
- public NetworkProvider getNetworkProvider(Context context, Looper looper) {
- return new NetworkProvider(context, looper, TAG);
- }
-
/** Get the HandlerThread for L2capNetworkProvider to run on */
public HandlerThread getHandlerThread() {
final HandlerThread thread = new HandlerThread("L2capNetworkProviderThread");
thread.start();
return thread;
}
+
+ @Nullable
+ public ParcelFileDescriptor createTunInterface(String ifname) {
+ final ParcelFileDescriptor fd;
+ try {
+ fd = ParcelFileDescriptor.adoptFd(ServiceConnectivityJni.createTunTap(
+ true /*isTun*/,
+ true /*hasCarrier*/,
+ true /*setIffMulticast*/,
+ ifname));
+ ServiceConnectivityJni.bringUpInterface(ifname);
+ // TODO: consider adding a parameter to createTunTap() (or the Builder that should
+ // be added) to configure i/o blocking.
+ final int flags = Os.fcntlInt(fd.getFileDescriptor(), F_GETFL, 0);
+ Os.fcntlInt(fd.getFileDescriptor(), F_SETFL, flags & ~O_NONBLOCK);
+ } catch (Exception e) {
+ // Note: createTunTap currently throws an IllegalStateException on failure.
+ // TODO: native functions should throw ErrnoException.
+ Log.e(TAG, "Failed to create tun interface", e);
+ return null;
+ }
+ return fd;
+ }
}
public L2capNetworkProvider(Context context) {
@@ -677,11 +663,9 @@
mContext = context;
mHandlerThread = mDeps.getHandlerThread();
mHandler = new Handler(mHandlerThread.getLooper());
- mProvider = mDeps.getNetworkProvider(context, mHandlerThread.getLooper());
+ mProvider = new NetworkProvider(context, mHandlerThread.getLooper(), TAG);
mBlanketOffer = new BlanketReservationOffer();
mClientOffer = new ClientOffer();
- mBluetoothManager = context.getSystemService(BluetoothManager.class);
- mIsSupported = mContext.getPackageManager().hasSystemFeature(FEATURE_BLUETOOTH_LE);
}
/**
@@ -690,19 +674,17 @@
* 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);
diff --git a/service/src/com/android/server/net/HeaderCompressionUtils.java b/service/src/com/android/server/net/HeaderCompressionUtils.java
new file mode 100644
index 0000000..5bd3a76
--- /dev/null
+++ b/service/src/com/android/server/net/HeaderCompressionUtils.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2025 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.net;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+public class HeaderCompressionUtils {
+ private static final String TAG = "L2capHeaderCompressionUtils";
+ private static final int IPV6_HEADER_SIZE = 40;
+
+ private static byte[] decodeIpv6Address(ByteBuffer buffer, int mode, boolean isMulticast)
+ throws BufferUnderflowException, IOException {
+ // Mode is equivalent between SAM and DAM; however, isMulticast only applies to DAM.
+ final byte[] address = new byte[16];
+ // If multicast bit is set, mix it in the mode, so that the lower two bits represent the
+ // address mode, and the upper bit represents multicast compression.
+ switch ((isMulticast ? 0b100 : 0) | mode) {
+ case 0b000: // 128 bits. The full address is carried in-line.
+ case 0b100:
+ buffer.get(address);
+ break;
+ case 0b001: // 64 bits. The first 64-bits of the fe80:: address are elided.
+ address[0] = (byte) 0xfe;
+ address[1] = (byte) 0x80;
+ buffer.get(address, 8 /*off*/, 8 /*len*/);
+ break;
+ case 0b010: // 16 bits. fe80::ff:fe00:XXXX, where XXXX are the bits carried in-line
+ address[0] = (byte) 0xfe;
+ address[1] = (byte) 0x80;
+ address[11] = (byte) 0xff;
+ address[12] = (byte) 0xfe;
+ buffer.get(address, 14 /*off*/, 2 /*len*/);
+ break;
+ case 0b011: // 0 bits. The address is fully elided and derived from BLE MAC address
+ // Note that on Android, the BLE MAC addresses are not exposed via the API;
+ // therefore, this compression mode cannot be supported.
+ throw new IOException("Address cannot be fully elided");
+ case 0b101: // 48 bits. The address takes the form ffXX::00XX:XXXX:XXXX.
+ address[0] = (byte) 0xff;
+ address[1] = buffer.get();
+ buffer.get(address, 11 /*off*/, 5 /*len*/);
+ break;
+ case 0b110: // 32 bits. The address takes the form ffXX::00XX:XXXX
+ address[0] = (byte) 0xff;
+ address[1] = buffer.get();
+ buffer.get(address, 13 /*off*/, 3 /*len*/);
+ break;
+ case 0b111: // 8 bits. The address takes the form ff02::00XX.
+ address[0] = (byte) 0xff;
+ address[1] = (byte) 0x02;
+ address[15] = buffer.get();
+ break;
+ }
+ return address;
+ }
+
+ /**
+ * Performs 6lowpan header decompression in place.
+ *
+ * Note that the passed in buffer must have enough capacity for successful decompression.
+ *
+ * @param bytes The buffer containing the packet.
+ * @param len The size of the packet
+ * @return decompressed size or zero
+ * @throws BufferUnderflowException if an illegal packet is encountered.
+ * @throws IOException if an unsupported option is encountered.
+ */
+ public static int decompress6lowpan(byte[] bytes, int len)
+ throws BufferUnderflowException, IOException {
+ // Note that ByteBuffer's default byte order is big endian.
+ final ByteBuffer inBuffer = ByteBuffer.wrap(bytes);
+ inBuffer.limit(len);
+
+ // LOWPAN_IPHC base encoding:
+ // 0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15
+ // +---+---+---+---+---+---+---+---|---+---+---+---+---+---+---+---+
+ // | 0 | 1 | 1 | TF |NH | HLIM |CID|SAC| SAM | M |DAC| DAM |
+ // +---+---+---+---+---+---+---+---|---+---+---+---+---+---+---+---+
+ final int iphc1 = inBuffer.get() & 0xff;
+ final int iphc2 = inBuffer.get() & 0xff;
+ // Dispatch must start with 0b011.
+ if ((iphc1 & 0xe0) != 0x60) {
+ throw new IOException("LOWPAN_IPHC does not start with 011");
+ }
+
+ final int tf = (iphc1 >> 3) & 3; // Traffic class
+ final boolean nh = (iphc1 & 4) != 0; // Next header
+ final int hlim = iphc1 & 3; // Hop limit
+ final boolean cid = (iphc2 & 0x80) != 0; // Context identifier extension
+ final boolean sac = (iphc2 & 0x40) != 0; // Source address compression
+ final int sam = (iphc2 >> 4) & 3; // Source address mode
+ final boolean m = (iphc2 & 8) != 0; // Multicast compression
+ final boolean dac = (iphc2 & 4) != 0; // Destination address compression
+ final int dam = iphc2 & 3; // Destination address mode
+
+ final ByteBuffer ipv6Header = ByteBuffer.allocate(IPV6_HEADER_SIZE);
+
+ final int trafficClass;
+ final int flowLabel;
+ switch (tf) {
+ case 0b00: // ECN + DSCP + 4-bit Pad + Flow Label (4 bytes)
+ trafficClass = inBuffer.get() & 0xff;
+ flowLabel = (inBuffer.get() & 0x0f) << 16
+ | (inBuffer.get() & 0xff) << 8
+ | (inBuffer.get() & 0xff);
+ break;
+ case 0b01: // ECN + 2-bit Pad + Flow Label (3 bytes), DSCP is elided.
+ final int firstByte = inBuffer.get() & 0xff;
+ // 0 1 2 3 4 5 6 7
+ // +-----+-----+-----+-----+-----+-----+-----+-----+
+ // | DS FIELD, DSCP | ECN FIELD |
+ // +-----+-----+-----+-----+-----+-----+-----+-----+
+ // rfc6282 does not explicitly state what value to use for DSCP, assuming 0.
+ trafficClass = firstByte >> 6;
+ flowLabel = (firstByte & 0x0f) << 16
+ | (inBuffer.get() & 0xff) << 8
+ | (inBuffer.get() & 0xff);
+ break;
+ case 0b10: // ECN + DSCP (1 byte), Flow Label is elided.
+ trafficClass = inBuffer.get() & 0xff;
+ // rfc6282 does not explicitly state what value to use, assuming 0.
+ flowLabel = 0;
+ break;
+ case 0b11: // Traffic Class and Flow Label are elided.
+ // rfc6282 does not explicitly state what value to use, assuming 0.
+ trafficClass = 0;
+ flowLabel = 0;
+ break;
+ default:
+ // This cannot happen. Crash if it does.
+ throw new IllegalStateException("Illegal TF value");
+ }
+
+ // Write version, traffic class, and flow label
+ final int versionTcFlowLabel = (6 << 28) | (trafficClass << 20) | flowLabel;
+ ipv6Header.putInt(versionTcFlowLabel);
+
+ // Payload length is still unknown. Use 0 for now.
+ ipv6Header.putShort((short) 0);
+
+ // We do not use UDP or extension header compression, therefore the next header
+ // cannot be compressed.
+ if (nh) throw new IOException("Next header cannot be compressed");
+ // Write next header
+ ipv6Header.put(inBuffer.get());
+
+ final byte hopLimit;
+ switch (hlim) {
+ case 0b00: // The Hop Limit field is carried in-line.
+ hopLimit = inBuffer.get();
+ break;
+ case 0b01: // The Hop Limit field is compressed and the hop limit is 1.
+ hopLimit = 1;
+ break;
+ case 0b10: // The Hop Limit field is compressed and the hop limit is 64.
+ hopLimit = 64;
+ break;
+ case 0b11: // The Hop Limit field is compressed and the hop limit is 255.
+ hopLimit = (byte) 255;
+ break;
+ default:
+ // This cannot happen. Crash if it does.
+ throw new IllegalStateException("Illegal HLIM value");
+ }
+ ipv6Header.put(hopLimit);
+
+ if (cid) throw new IOException("Context based compression not supported");
+ if (sac) throw new IOException("Context based compression not supported");
+ if (dac) throw new IOException("Context based compression not supported");
+
+ // Write source address
+ ipv6Header.put(decodeIpv6Address(inBuffer, sam, false /* isMulticast */));
+
+ // Write destination address
+ ipv6Header.put(decodeIpv6Address(inBuffer, dam, m));
+
+ // Go back and fix up payloadLength
+ final short payloadLength = (short) inBuffer.remaining();
+ ipv6Header.putShort(4, payloadLength);
+
+ // Done! Check that 40 bytes were written.
+ if (ipv6Header.position() != IPV6_HEADER_SIZE) {
+ // This indicates a bug in our code -> crash.
+ throw new IllegalStateException("Faulty decompression wrote less than 40 bytes");
+ }
+
+ // Ensure there is enough room in the buffer
+ final int packetLength = payloadLength + IPV6_HEADER_SIZE;
+ if (bytes.length < packetLength) {
+ throw new IOException("Decompressed packet exceeds buffer size");
+ }
+
+ // Move payload bytes back to make room for the header
+ inBuffer.limit(packetLength);
+ System.arraycopy(bytes, inBuffer.position(), bytes, IPV6_HEADER_SIZE, payloadLength);
+ // Copy IPv6 header to the beginning of the buffer.
+ inBuffer.position(0);
+ ipv6Header.flip();
+ inBuffer.put(ipv6Header);
+
+ return packetLength;
+ }
+
+ /**
+ * Performs 6lowpan header compression in place.
+ *
+ * @param bytes The buffer containing the packet.
+ * @param len The size of the packet
+ * @return compressed size or zero
+ * @throws BufferUnderflowException if an illegal packet is encountered.
+ * @throws IOException if an unsupported option is encountered.
+ */
+ public static int compress6lowpan(byte[] bytes, final int len)
+ throws BufferUnderflowException, IOException {
+ // Compression only happens on egress, i.e. the packet is read from the tun fd.
+ // This means that this code can be a bit more lenient.
+ if (len < 40) {
+ Log.wtf(TAG, "Encountered short (<40 byte) packet");
+ return 0;
+ }
+
+ // Note that ByteBuffer's default byte order is big endian.
+ final ByteBuffer inBuffer = ByteBuffer.wrap(bytes);
+ inBuffer.limit(len);
+
+ // Check that the packet is an IPv6 packet
+ final int versionTcFlowLabel = inBuffer.getInt() & 0xffffffff;
+ if ((versionTcFlowLabel >> 28) != 6) {
+ return 0;
+ }
+
+ // Check that the payload length matches the packet length - 40.
+ int payloadLength = inBuffer.getShort();
+ if (payloadLength != len - IPV6_HEADER_SIZE) {
+ throw new IOException("Encountered packet with payload length mismatch");
+ }
+
+ // Implements rfc 6282 6lowpan header compression using iphc 0110 0000 0000 0000 (all
+ // fields are carried inline).
+ inBuffer.position(0);
+ inBuffer.put((byte) 0x60);
+ inBuffer.put((byte) 0x00);
+ final byte trafficClass = (byte) ((versionTcFlowLabel >> 20) & 0xff);
+ inBuffer.put(trafficClass);
+ final byte flowLabelMsb = (byte) ((versionTcFlowLabel >> 16) & 0x0f);
+ final short flowLabelLsb = (short) (versionTcFlowLabel & 0xffff);
+ inBuffer.put(flowLabelMsb);
+ // Note: the next putShort overrides the payload length. This is WAI as the payload length
+ // is reconstructed via L2CAP packet length.
+ inBuffer.putShort(flowLabelLsb);
+
+ // Since the iphc (2 bytes) matches the payload length that was elided (2 bytes), the length
+ // of the packet did not change.
+ return len;
+ }
+}
diff --git a/service/src/com/android/server/net/L2capNetwork.java b/service/src/com/android/server/net/L2capNetwork.java
index b9d5f13..b624bca 100644
--- a/service/src/com/android/server/net/L2capNetwork.java
+++ b/service/src/com/android/server/net/L2capNetwork.java
@@ -16,9 +16,12 @@
package com.android.server.net;
+import static android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_6LOWPAN;
+
import android.annotation.Nullable;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
+import android.net.L2capNetworkSpecifier;
import android.net.LinkProperties;
import android.net.NetworkAgent;
import android.net.NetworkAgentConfig;
@@ -39,10 +42,8 @@
private static final NetworkScore NETWORK_SCORE = new NetworkScore.Builder().build();
private final String mLogTag;
private final Handler mHandler;
- private final String mIfname;
private final L2capPacketForwarder mForwarder;
private final NetworkCapabilities mNetworkCapabilities;
- private final L2capIpClient mIpClient;
private final NetworkAgent mNetworkAgent;
/** IpClient wrapper to handle IPv6 link-local provisioning for L2CAP tun.
@@ -56,7 +57,7 @@
@Nullable
private IpClientManager mIpClient;
@Nullable
- private LinkProperties mLinkProperties;
+ private volatile LinkProperties mLinkProperties;
L2capIpClient(String logTag, Context context, String ifname) {
mLogTag = logTag;
@@ -71,11 +72,24 @@
@Override
public void onProvisioningSuccess(LinkProperties lp) {
- Log.d(mLogTag, "Successfully provisionined l2cap tun: " + lp);
+ Log.d(mLogTag, "Successfully provisioned l2cap tun: " + lp);
mLinkProperties = lp;
mOnProvisioningSuccessCv.open();
}
+ @Override
+ public void onProvisioningFailure(LinkProperties lp) {
+ Log.i(mLogTag, "Failed to provision l2cap tun: " + lp);
+ mLinkProperties = null;
+ mOnProvisioningSuccessCv.open();
+ }
+
+ /**
+ * Starts IPv6 link-local provisioning.
+ *
+ * @return LinkProperties on success, null on failure.
+ */
+ @Nullable
public LinkProperties start() {
mOnIpClientCreatedCv.block();
// mIpClient guaranteed non-null.
@@ -99,27 +113,27 @@
void onNetworkUnwanted(L2capNetwork network);
}
- public L2capNetwork(Handler handler, Context context, NetworkProvider provider, String ifname,
- BluetoothSocket socket, ParcelFileDescriptor tunFd,
- NetworkCapabilities networkCapabilities, ICallback cb) {
- // TODO: add a check that this constructor is invoked on the handler thread.
- mLogTag = String.format("L2capNetwork[%s]", ifname);
+ public L2capNetwork(String logTag, Handler handler, Context context, NetworkProvider provider,
+ BluetoothSocket socket, ParcelFileDescriptor tunFd, NetworkCapabilities nc,
+ LinkProperties lp, ICallback cb) {
+ mLogTag = logTag;
mHandler = handler;
- mIfname = ifname;
- mForwarder = new L2capPacketForwarder(handler, tunFd, socket, () -> {
+ mNetworkCapabilities = nc;
+
+ final L2capNetworkSpecifier spec = (L2capNetworkSpecifier) nc.getNetworkSpecifier();
+ final boolean compressHeaders = spec.getHeaderCompression() == HEADER_COMPRESSION_6LOWPAN;
+
+ mForwarder = new L2capPacketForwarder(handler, tunFd, socket, compressHeaders, () -> {
// TODO: add a check that this callback is invoked on the handler thread.
cb.onError(L2capNetwork.this);
});
- mNetworkCapabilities = networkCapabilities;
- mIpClient = new L2capIpClient(mLogTag, context, ifname);
- final LinkProperties linkProperties = mIpClient.start();
final NetworkAgentConfig config = new NetworkAgentConfig.Builder().build();
mNetworkAgent = new NetworkAgent(context, mHandler.getLooper(), mLogTag,
- networkCapabilities, linkProperties, NETWORK_SCORE, config, provider) {
+ nc, lp, NETWORK_SCORE, config, provider) {
@Override
public void onNetworkUnwanted() {
- Log.i(mLogTag, mIfname + ": Network is unwanted");
+ Log.i(mLogTag, "Network is unwanted");
// TODO: add a check that this callback is invoked on the handler thread.
cb.onNetworkUnwanted(L2capNetwork.this);
}
@@ -128,6 +142,24 @@
mNetworkAgent.markConnected();
}
+ /** Create an L2capNetwork or return null on failure. */
+ @Nullable
+ public static L2capNetwork create(Handler handler, Context context, NetworkProvider provider,
+ String ifname, BluetoothSocket socket, ParcelFileDescriptor tunFd,
+ NetworkCapabilities nc, ICallback cb) {
+ // TODO: add a check that this function is invoked on the handler thread.
+ final String logTag = String.format("L2capNetwork[%s]", ifname);
+
+ // L2capIpClient#start() blocks until provisioning either succeeds (and returns
+ // LinkProperties) or fails (and returns null).
+ // Note that since L2capNetwork is using IPv6 link-local provisioning the most likely
+ // (only?) failure mode is due to the interface disappearing.
+ final LinkProperties lp = new L2capIpClient(logTag, context, ifname).start();
+ if (lp == null) return null;
+
+ return new L2capNetwork(logTag, handler, context, provider, socket, tunFd, nc, lp, cb);
+ }
+
/** Get the NetworkCapabilities used for this Network */
public NetworkCapabilities getNetworkCapabilities() {
return mNetworkCapabilities;
diff --git a/service/src/com/android/server/net/L2capPacketForwarder.java b/service/src/com/android/server/net/L2capPacketForwarder.java
index cef351c..00faecf 100644
--- a/service/src/com/android/server/net/L2capPacketForwarder.java
+++ b/service/src/com/android/server/net/L2capPacketForwarder.java
@@ -16,6 +16,9 @@
package com.android.server.net;
+import static com.android.server.net.HeaderCompressionUtils.compress6lowpan;
+import static com.android.server.net.HeaderCompressionUtils.decompress6lowpan;
+
import android.bluetooth.BluetoothSocket;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
@@ -29,6 +32,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.BufferUnderflowException;
/**
* Forwards packets from a BluetoothSocket of type L2CAP to a tun fd and vice versa.
@@ -222,13 +226,18 @@
private volatile boolean mIsRunning = true;
private final String mLogTag;
- private IReadWriteFd mReadFd;
- private IReadWriteFd mWriteFd;
+ private final IReadWriteFd mReadFd;
+ private final IReadWriteFd mWriteFd;
+ private final boolean mIsIngress;
+ private final boolean mCompressHeaders;
- L2capThread(String logTag, IReadWriteFd readFd, IReadWriteFd writeFd) {
- mLogTag = logTag;
+ L2capThread(IReadWriteFd readFd, IReadWriteFd writeFd, boolean isIngress,
+ boolean compressHeaders) {
+ mLogTag = isIngress ? "L2capForwarderThread-Ingress" : "L2capForwarderThread-Egress";
mReadFd = readFd;
mWriteFd = writeFd;
+ mIsIngress = isIngress;
+ mCompressHeaders = compressHeaders;
}
private void postOnError() {
@@ -242,20 +251,28 @@
public void run() {
while (mIsRunning) {
try {
- final int readBytes = mReadFd.read(mBuffer, 0 /*off*/, mBuffer.length);
+ int readBytes = mReadFd.read(mBuffer, 0 /*off*/, mBuffer.length);
// No bytes to write, continue.
if (readBytes <= 0) {
Log.w(mLogTag, "Zero-byte read encountered: " + readBytes);
continue;
}
- // If the packet exceeds MTU, drop it.
+ if (mCompressHeaders) {
+ if (mIsIngress) {
+ readBytes = decompress6lowpan(mBuffer, readBytes);
+ } else {
+ readBytes = compress6lowpan(mBuffer, readBytes);
+ }
+ }
+
+ // If the packet is 0-length post de/compression or exceeds MTU, drop it.
// Note that a large read on BluetoothSocket throws an IOException to tear down
// the network.
- if (readBytes > MTU) continue;
+ if (readBytes <= 0 || readBytes > MTU) continue;
mWriteFd.write(mBuffer, 0 /*off*/, readBytes);
- } catch (IOException e) {
+ } catch (IOException|BufferUnderflowException e) {
Log.e(mLogTag, "L2capThread exception", e);
// Tear down the network on any error.
mIsRunning = false;
@@ -273,19 +290,20 @@
}
public L2capPacketForwarder(Handler handler, ParcelFileDescriptor tunFd, BluetoothSocket socket,
- ICallback cb) {
- this(handler, new FdWrapper(tunFd), new BluetoothSocketWrapper(socket), cb);
+ boolean compressHdrs, ICallback cb) {
+ this(handler, new FdWrapper(tunFd), new BluetoothSocketWrapper(socket), compressHdrs, cb);
}
@VisibleForTesting
- L2capPacketForwarder(Handler handler, IReadWriteFd tunFd, IReadWriteFd l2capFd, ICallback cb) {
+ L2capPacketForwarder(Handler handler, IReadWriteFd tunFd, IReadWriteFd l2capFd,
+ boolean compressHeaders, ICallback cb) {
mHandler = handler;
mTunFd = tunFd;
mL2capFd = l2capFd;
mCallback = cb;
- mIngressThread = new L2capThread("L2capThread-Ingress", l2capFd, tunFd);
- mEgressThread = new L2capThread("L2capThread-Egress", tunFd, l2capFd);
+ mIngressThread = new L2capThread(l2capFd, tunFd, true /*isIngress*/, compressHeaders);
+ mEgressThread = new L2capThread(tunFd, l2capFd, false /*isIngress*/, compressHeaders);
mIngressThread.start();
mEgressThread.start();
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
index ee2e7db..8e790ca 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -489,15 +489,15 @@
fun ApfV4GeneratorBase<*>.addPassIfNotIcmpv6EchoReply() {
// If not IPv6 -> PASS
- addLoad16(R0, ETH_ETHERTYPE_OFFSET)
+ addLoad16intoR0(ETH_ETHERTYPE_OFFSET)
addJumpIfR0NotEquals(ETH_P_IPV6.toLong(), BaseApfGenerator.PASS_LABEL)
// If not ICMPv6 -> PASS
- addLoad8(R0, IPV6_NEXT_HEADER_OFFSET)
+ addLoad8intoR0(IPV6_NEXT_HEADER_OFFSET)
addJumpIfR0NotEquals(IPPROTO_ICMPV6.toLong(), BaseApfGenerator.PASS_LABEL)
// If not echo reply -> PASS
- addLoad8(R0, ICMP6_TYPE_OFFSET)
+ addLoad8intoR0(ICMP6_TYPE_OFFSET)
addJumpIfR0NotEquals(0x81, BaseApfGenerator.PASS_LABEL)
}
@@ -744,11 +744,11 @@
// transmit 3 ICMPv6 echo requests with random first byte
// increase DROPPED_IPV6_NS_REPLIED_NON_DAD counter
// drop
- gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET)
+ gen.addLoad16intoR0(ETH_ETHERTYPE_OFFSET)
.addJumpIfR0NotEquals(ETH_P_IPV6.toLong(), skipPacketLabel)
- .addLoad8(R0, IPV6_NEXT_HEADER_OFFSET)
+ .addLoad8intoR0(IPV6_NEXT_HEADER_OFFSET)
.addJumpIfR0NotEquals(IPPROTO_ICMPV6.toLong(), skipPacketLabel)
- .addLoad8(R0, ICMP6_TYPE_OFFSET)
+ .addLoad8intoR0(ICMP6_TYPE_OFFSET)
.addJumpIfR0NotEquals(ICMP6_ECHO_REPLY.toLong(), skipPacketLabel)
.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE)
.addCountAndPassIfR0Equals(
diff --git a/tests/cts/net/src/android/net/cts/NetworkReservationTest.kt b/tests/cts/net/src/android/net/cts/NetworkReservationTest.kt
index f05bf15..a9af34f 100644
--- a/tests/cts/net/src/android/net/cts/NetworkReservationTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkReservationTest.kt
@@ -16,15 +16,21 @@
package android.net.cts
+import android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS
import android.Manifest.permission.MANAGE_TEST_NETWORKS
import android.Manifest.permission.NETWORK_SETTINGS
import android.net.ConnectivityManager
+import android.net.L2capNetworkSpecifier
+import android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_6LOWPAN
+import android.net.L2capNetworkSpecifier.ROLE_SERVER
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
import android.net.NetworkCapabilities.RES_ID_MATCH_ALL_RESERVATIONS
+import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
import android.net.NetworkCapabilities.TRANSPORT_TEST
import android.net.NetworkProvider
@@ -43,7 +49,10 @@
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.TestableNetworkOfferCallback
import com.android.testutils.runAsShell
+import kotlin.test.assertContains
import kotlin.test.assertEquals
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -83,6 +92,8 @@
private val handler = Handler(handlerThread.looper)
private val provider = NetworkProvider(context, handlerThread.looper, TAG)
+ private val registeredCallbacks = ArrayList<TestableNetworkCallback>()
+
@Before
fun setUp() {
runAsShell(NETWORK_SETTINGS) {
@@ -92,6 +103,7 @@
@After
fun tearDown() {
+ registeredCallbacks.forEach { cm.unregisterNetworkCallback(it) }
runAsShell(NETWORK_SETTINGS) {
// unregisterNetworkProvider unregisters all associated NetworkOffers.
cm.unregisterNetworkProvider(provider)
@@ -104,6 +116,13 @@
it.reservationId = resId
}
+ fun reserveNetwork(nr: NetworkRequest): TestableNetworkCallback {
+ return TestableNetworkCallback().also {
+ cm.reserveNetwork(nr, handler, it)
+ registeredCallbacks.add(it)
+ }
+ }
+
@Test
fun testReserveNetwork() {
// register blanket offer
@@ -112,8 +131,7 @@
provider.registerNetworkOffer(NETWORK_SCORE, BLANKET_CAPS, handler::post, blanketOffer)
}
- val cb = TestableNetworkCallback()
- cm.reserveNetwork(ETHERNET_REQUEST, handler, cb)
+ val cb = reserveNetwork(ETHERNET_REQUEST)
// validate the reservation matches the blanket offer.
val reservationReq = blanketOffer.expectOnNetworkNeeded(BLANKET_CAPS).request
@@ -137,4 +155,28 @@
provider.unregisterNetworkOffer(reservedOffer)
cb.expect<Unavailable>()
}
+
+ @Test
+ fun testReserveL2capNetwork() {
+ val l2capReservationSpecifier = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_SERVER)
+ .setHeaderCompression(HEADER_COMPRESSION_6LOWPAN)
+ .build()
+ val l2capRequest = NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_BLUETOOTH)
+ .removeCapability(NET_CAPABILITY_TRUSTED)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .setNetworkSpecifier(l2capReservationSpecifier)
+ .build()
+ val cb = runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS) {
+ reserveNetwork(l2capRequest)
+ }
+
+ val caps = cb.expect<Reserved>().caps
+ val reservedSpec = caps.networkSpecifier
+ assertTrue(reservedSpec is L2capNetworkSpecifier)
+ assertContains(0x80..0xFF, reservedSpec.psm, "PSM is outside of dynamic range")
+ assertEquals(HEADER_COMPRESSION_6LOWPAN, reservedSpec.headerCompression)
+ assertNull(reservedSpec.remoteAddress)
+ }
}
diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index 375d604..cc9445d 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -55,6 +55,7 @@
import com.android.networkstack.apishim.TelephonyManagerShimImpl
import com.android.server.BpfNetMaps
import com.android.server.ConnectivityService
+import com.android.server.L2capNetworkProvider
import com.android.server.NetworkAgentWrapper
import com.android.server.TestNetIdManager
import com.android.server.connectivity.CarrierPrivilegeAuthenticator
@@ -272,6 +273,8 @@
connectivityServiceInternalHandler: Handler
): SatelliteAccessController? = mock(
SatelliteAccessController::class.java)
+
+ override fun makeL2capNetworkProvider(context: Context) = null
}
@After
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index f7d7c87..1562dad 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -412,6 +412,7 @@
import com.android.server.ConnectivityService.NetworkRequestInfo;
import com.android.server.ConnectivityServiceTest.ConnectivityServiceDependencies.DestroySocketsWrapper;
import com.android.server.ConnectivityServiceTest.ConnectivityServiceDependencies.ReportedInterfaces;
+import com.android.server.L2capNetworkProvider;
import com.android.server.connectivity.ApplicationSelfCertifiedNetworkCapabilities;
import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker;
import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
@@ -2381,6 +2382,11 @@
// Needed to mock out the dependency on DeviceConfig
return 15;
}
+
+ @Override
+ public L2capNetworkProvider makeL2capNetworkProvider(Context context) {
+ return null;
+ }
}
private class AutomaticOnOffKeepaliveTrackerDependencies
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt
new file mode 100644
index 0000000..d44bd0a
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt
@@ -0,0 +1,191 @@
+/*
+ * 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.bluetooth.BluetoothSocket
+import android.content.Context
+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.NET_CAPABILITY_NOT_RESTRICTED
+import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
+import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH
+import android.net.NetworkProvider
+import android.net.NetworkProvider.NetworkOfferCallback
+import android.net.NetworkRequest
+import android.net.NetworkScore
+import android.net.NetworkSpecifier
+import android.os.Build
+import android.os.HandlerThread
+import android.os.Looper
+import com.android.server.L2capNetworkProvider
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry.Reserved
+import com.android.testutils.TestableNetworkCallback
+import java.io.IOException
+import java.util.concurrent.Executor
+import java.util.concurrent.LinkedBlockingQueue
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+private const val PSM = 0x85
+private val REMOTE_MAC = byteArrayOf(1, 2, 3, 4, 5, 6)
+private val REQUEST = NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_BLUETOOTH)
+ .removeCapability(NET_CAPABILITY_TRUSTED)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .build()
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.R)
+class CSL2capProviderTest : CSTest() {
+ private val btAdapter = mock<BluetoothAdapter>()
+ private val btServerSocket = mock<BluetoothServerSocket>()
+ private val btSocket = mock<BluetoothSocket>()
+ private val providerDeps = mock<L2capNetworkProvider.Dependencies>()
+ private val acceptQueue = LinkedBlockingQueue<BluetoothSocket>()
+
+ private val handlerThread = HandlerThread("CSL2capProviderTest thread").apply { start() }
+
+ // Requires Dependencies mock to be setup before creation.
+ private lateinit var provider: L2capNetworkProvider
+
+ @Before
+ fun innerSetUp() {
+ doReturn(btAdapter).`when`(bluetoothManager).getAdapter()
+ doReturn(btServerSocket).`when`(btAdapter).listenUsingInsecureL2capChannel()
+ doReturn(PSM).`when`(btServerSocket).getPsm();
+
+ doAnswer {
+ val sock = acceptQueue.take()
+ sock ?: throw IOException()
+ }.`when`(btServerSocket).accept()
+
+ doAnswer {
+ acceptQueue.put(null)
+ }.`when`(btServerSocket).close()
+
+ doReturn(handlerThread).`when`(providerDeps).getHandlerThread()
+ provider = L2capNetworkProvider(providerDeps, context)
+ provider.start()
+ }
+
+ @After
+ fun innerTearDown() {
+ handlerThread.quitSafely()
+ handlerThread.join()
+ }
+
+ private fun reserveNetwork(nr: NetworkRequest) = TestableNetworkCallback().also {
+ cm.reserveNetwork(nr, csHandler, it)
+ }
+
+ private fun requestNetwork(nr: NetworkRequest) = TestableNetworkCallback().also {
+ cm.requestNetwork(nr, it, csHandler)
+ }
+
+ private fun NetworkRequest.copyWithSpecifier(specifier: NetworkSpecifier): NetworkRequest {
+ // Note: NetworkRequest.Builder(NetworkRequest) *does not* perform a defensive copy but
+ // changes the underlying request.
+ return NetworkRequest.Builder(NetworkRequest(this))
+ .setNetworkSpecifier(specifier)
+ .build()
+ }
+
+ @Test
+ fun testReservation() {
+ val l2capServerSpecifier = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_SERVER)
+ .setHeaderCompression(HEADER_COMPRESSION_6LOWPAN)
+ .build()
+ val l2capReservation = REQUEST.copyWithSpecifier(l2capServerSpecifier)
+ val reservationCb = reserveNetwork(l2capReservation)
+
+ val reservedCaps = reservationCb.expect<Reserved>().caps
+ val reservedSpec = reservedCaps.networkSpecifier as L2capNetworkSpecifier
+
+ assertEquals(PSM, reservedSpec.getPsm())
+ assertEquals(HEADER_COMPRESSION_6LOWPAN, reservedSpec.headerCompression)
+ assertNull(reservedSpec.remoteAddress)
+
+ reservationCb.assertNoCallback()
+ }
+
+ @Test
+ fun testBlanketOffer_reservationWithoutSpecifier() {
+ reserveNetwork(REQUEST).assertNoCallback()
+ }
+
+ @Test
+ fun testBlanketOffer_reservationWithCorrectSpecifier() {
+ var specifier = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_SERVER)
+ .setHeaderCompression(HEADER_COMPRESSION_6LOWPAN)
+ .build()
+ var nr = REQUEST.copyWithSpecifier(specifier)
+ reserveNetwork(nr).expect<Reserved>()
+
+ specifier = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_SERVER)
+ .setHeaderCompression(HEADER_COMPRESSION_NONE)
+ .build()
+ nr = REQUEST.copyWithSpecifier(specifier)
+ reserveNetwork(nr).expect<Reserved>()
+ }
+
+ @Test
+ fun testBlanketOffer_reservationWithIncorrectSpecifier() {
+ var specifier = L2capNetworkSpecifier.Builder().build()
+ var nr = REQUEST.copyWithSpecifier(specifier)
+ reserveNetwork(nr).assertNoCallback()
+
+ specifier = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_SERVER)
+ .build()
+ nr = REQUEST.copyWithSpecifier(specifier)
+ reserveNetwork(nr).assertNoCallback()
+
+ specifier = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_SERVER)
+ .setHeaderCompression(HEADER_COMPRESSION_NONE)
+ .setPsm(0x81)
+ .build()
+ nr = REQUEST.copyWithSpecifier(specifier)
+ reserveNetwork(nr).assertNoCallback()
+
+ specifier = L2capNetworkSpecifier.Builder()
+ .setHeaderCompression(HEADER_COMPRESSION_NONE)
+ .build()
+ nr = REQUEST.copyWithSpecifier(specifier)
+ reserveNetwork(nr).assertNoCallback()
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index ae196a6..350a140 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -18,6 +18,7 @@
import android.app.AlarmManager
import android.app.AppOpsManager
+import android.bluetooth.BluetoothManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -209,6 +210,7 @@
doReturn(true).`when`(it).isDataCapable()
}
val subscriptionManager = mock<SubscriptionManager>()
+ val bluetoothManager = mock<BluetoothManager>()
val multicastRoutingCoordinatorService = mock<MulticastRoutingCoordinatorService>()
val satelliteAccessController = mock<SatelliteAccessController>()
@@ -393,6 +395,8 @@
// Call mocked destroyLiveTcpSocketsByOwnerUids so that test can verify this method call
destroySocketsWrapper.destroyLiveTcpSocketsByOwnerUids(ownerUids)
}
+
+ override fun makeL2capNetworkProvider(context: Context) = null
}
inner class CSContext(base: Context) : BroadcastInterceptingContext(base) {
@@ -503,6 +507,7 @@
Context.BATTERY_STATS_SERVICE -> batteryManager
Context.STATS_MANAGER -> null // Stats manager is final and can't be mocked
Context.APP_OPS_SERVICE -> appOpsManager
+ Context.BLUETOOTH_SERVICE -> bluetoothManager
else -> super.getSystemService(serviceName)
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTestHelpers.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTestHelpers.kt
index 8ff790c..6b560de 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTestHelpers.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTestHelpers.kt
@@ -23,6 +23,7 @@
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.PackageManager.FEATURE_BLUETOOTH
+import android.content.pm.PackageManager.FEATURE_BLUETOOTH_LE
import android.content.pm.PackageManager.FEATURE_ETHERNET
import android.content.pm.PackageManager.FEATURE_WIFI
import android.content.pm.PackageManager.FEATURE_WIFI_DIRECT
@@ -103,7 +104,13 @@
}
internal fun makeMockPackageManager(realContext: Context) = mock<PackageManager>().also { pm ->
- val supported = listOf(FEATURE_WIFI, FEATURE_WIFI_DIRECT, FEATURE_BLUETOOTH, FEATURE_ETHERNET)
+ val supported = listOf(
+ FEATURE_WIFI,
+ FEATURE_WIFI_DIRECT,
+ FEATURE_BLUETOOTH,
+ FEATURE_BLUETOOTH_LE,
+ FEATURE_ETHERNET
+ )
doReturn(true).`when`(pm).hasSystemFeature(argThat { supported.contains(it) })
val myPackageName = realContext.packageName
val myPackageInfo = realContext.packageManager.getPackageInfo(myPackageName,
diff --git a/tests/unit/java/com/android/server/net/HeaderCompressionUtilsTest.kt b/tests/unit/java/com/android/server/net/HeaderCompressionUtilsTest.kt
new file mode 100644
index 0000000..8431194
--- /dev/null
+++ b/tests/unit/java/com/android/server/net/HeaderCompressionUtilsTest.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2025 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.net
+
+import android.os.Build
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.internal.util.HexDump
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TIMEOUT = 1000L
+
+@ConnectivityModuleTest
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+class HeaderCompressionUtilsTest {
+
+ private fun decompressHex(hex: String): ByteArray {
+ val bytes = HexDump.hexStringToByteArray(hex)
+ val buf = bytes.copyOf(1500)
+ val newLen = HeaderCompressionUtils.decompress6lowpan(buf, bytes.size)
+ return buf.copyOf(newLen)
+ }
+
+ private fun compressHex(hex: String): ByteArray {
+ val buf = HexDump.hexStringToByteArray(hex)
+ val newLen = HeaderCompressionUtils.compress6lowpan(buf, buf.size)
+ return buf.copyOf(newLen)
+ }
+
+ private fun String.decodeHex() = HexDump.hexStringToByteArray(this)
+
+ @Test
+ fun testHeaderDecompression() {
+ // TF: 00, NH: 0, HLIM: 00, CID: 0, SAC: 0, SAM: 00, M: 0, DAC: 0, DAM: 00
+ var input = "6000" +
+ "ccf" + // ECN + DSCP + 4-bit Pad (here "f")
+ "12345" + // flow label
+ "11" + // next header
+ "e7" + // hop limit
+ "abcdef1234567890abcdef1234567890" + // source
+ "aaabbbcccdddeeefff00011122233344" + // dest
+ "abcd" // payload
+
+ var output = "6" + // version
+ "cc" + // traffic class
+ "12345" + // flow label
+ "0002" + // payload length
+ "11" + // next header
+ "e7" + // hop limit
+ "abcdef1234567890abcdef1234567890" + // source
+ "aaabbbcccdddeeefff00011122233344" + // dest
+ "abcd" // payload
+ assertThat(decompressHex(input)).isEqualTo(output.decodeHex())
+
+ // TF: 01, NH: 0, HLIM: 01, CID: 0, SAC: 0, SAM: 01, M: 0, DAC: 0, DAM: 01
+ input = "6911" +
+ "5" + // ECN + 2-bit pad (here "1")
+ "f100e" + // flow label
+ "42" + // next header
+ "1102030405060708" + // source
+ "aa0b0c0d0e0f1011" + // dest
+ "abcd" // payload
+
+ output = "6" + // version
+ "01" + // traffic class
+ "f100e" + // flow label
+ "0002" + // payload length
+ "42" + // next header
+ "01" + // hop limit
+ "fe800000000000001102030405060708" + // source
+ "fe80000000000000aa0b0c0d0e0f1011" + // dest
+ "abcd" // payload
+ assertThat(decompressHex(input)).isEqualTo(output.decodeHex())
+
+ // TF: 10, NH: 0, HLIM: 10, CID: 0, SAC: 0, SAM: 10, M: 0, DAC: 0, DAM: 10
+ input = "7222" +
+ "cc" + // traffic class
+ "43" + // next header
+ "1234" + // source
+ "abcd" + // dest
+ "abcdef" // payload
+
+ output = "6" + // version
+ "cc" + // traffic class
+ "00000" + // flow label
+ "0003" + // payload length
+ "43" + // next header
+ "40" + // hop limit
+ "fe80000000000000000000fffe001234" + // source
+ "fe80000000000000000000fffe00abcd" + // dest
+ "abcdef" // payload
+ assertThat(decompressHex(input)).isEqualTo(output.decodeHex())
+
+ // TF: 11, NH: 0, HLIM: 11, CID: 0, SAC: 0, SAM: 10, M: 1, DAC: 0, DAM: 00
+ input = "7b28" +
+ "44" + // next header
+ "1234" + // source
+ "ff020000000000000000000000000001" + // dest
+ "abcdef01" // payload
+
+ output = "6" + // version
+ "00" + // traffic class
+ "00000" + // flow label
+ "0004" + // payload length
+ "44" + // next header
+ "ff" + // hop limit
+ "fe80000000000000000000fffe001234" + // source
+ "ff020000000000000000000000000001" + // dest
+ "abcdef01" // payload
+ assertThat(decompressHex(input)).isEqualTo(output.decodeHex())
+
+ // TF: 11, NH: 0, HLIM: 11, CID: 0, SAC: 0, SAM: 10, M: 1, DAC: 0, DAM: 01
+ input = "7b29" +
+ "44" + // next header
+ "1234" + // source
+ "02abcdef1234" + // dest
+ "abcdef01" // payload
+
+ output = "6" + // version
+ "00" + // traffic class
+ "00000" + // flow label
+ "0004" + // payload length
+ "44" + // next header
+ "ff" + // hop limit
+ "fe80000000000000000000fffe001234" + // source
+ "ff02000000000000000000abcdef1234" + // dest
+ "abcdef01" // payload
+ assertThat(decompressHex(input)).isEqualTo(output.decodeHex())
+
+ // TF: 11, NH: 0, HLIM: 11, CID: 0, SAC: 0, SAM: 10, M: 1, DAC: 0, DAM: 10
+ input = "7b2a" +
+ "44" + // next header
+ "1234" + // source
+ "ee123456" + // dest
+ "abcdef01" // payload
+
+ output = "6" + // version
+ "00" + // traffic class
+ "00000" + // flow label
+ "0004" + // payload length
+ "44" + // next header
+ "ff" + // hop limit
+ "fe80000000000000000000fffe001234" + // source
+ "ffee0000000000000000000000123456" + // dest
+ "abcdef01" // payload
+ assertThat(decompressHex(input)).isEqualTo(output.decodeHex())
+
+ // TF: 11, NH: 0, HLIM: 11, CID: 0, SAC: 0, SAM: 10, M: 1, DAC: 0, DAM: 11
+ input = "7b2b" +
+ "44" + // next header
+ "1234" + // source
+ "89" + // dest
+ "abcdef01" // payload
+
+ output = "6" + // version
+ "00" + // traffic class
+ "00000" + // flow label
+ "0004" + // payload length
+ "44" + // next header
+ "ff" + // hop limit
+ "fe80000000000000000000fffe001234" + // source
+ "ff020000000000000000000000000089" + // dest
+ "abcdef01" // payload
+ assertThat(decompressHex(input)).isEqualTo(output.decodeHex())
+ }
+
+ @Test
+ fun testHeaderCompression() {
+ val input = "60120304000011fffe800000000000000000000000000001fe800000000000000000000000000002"
+ val output = "60000102030411fffe800000000000000000000000000001fe800000000000000000000000000002"
+ assertThat(compressHex(input)).isEqualTo(output.decodeHex())
+ }
+}
diff --git a/tests/unit/java/com/android/server/net/L2capPacketForwarderTest.kt b/tests/unit/java/com/android/server/net/L2capPacketForwarderTest.kt
index b3095ee..e261732 100644
--- a/tests/unit/java/com/android/server/net/L2capPacketForwarderTest.kt
+++ b/tests/unit/java/com/android/server/net/L2capPacketForwarderTest.kt
@@ -184,6 +184,7 @@
handler,
FdWrapper(ParcelFileDescriptor(tunFds[0])),
BluetoothSocketWrapper(bluetoothSocket),
+ false /* compressHeaders */,
callback
)
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index b528480..581c709 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -168,7 +168,6 @@
import com.android.net.module.util.Struct.U8;
import com.android.net.module.util.bpf.CookieTagMapKey;
import com.android.net.module.util.bpf.CookieTagMapValue;
-import com.android.server.BpfNetMaps;
import com.android.server.connectivity.ConnectivityResources;
import com.android.server.net.NetworkStatsService.AlertObserver;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
@@ -286,8 +285,6 @@
private LocationPermissionChecker mLocationPermissionChecker;
private TestBpfMap<S32, U8> mUidCounterSetMap = spy(new TestBpfMap<>(S32.class, U8.class));
@Mock
- private BpfNetMaps mBpfNetMaps;
- @Mock
private SkDestroyListener mSkDestroyListener;
private TestBpfMap<CookieTagMapKey, CookieTagMapValue> mCookieTagMap = new TestBpfMap<>(
@@ -608,11 +605,6 @@
}
@Override
- public BpfNetMaps makeBpfNetMaps(Context ctx) {
- return mBpfNetMaps;
- }
-
- @Override
public SkDestroyListener makeSkDestroyListener(
IBpfMap<CookieTagMapKey, CookieTagMapValue> cookieTagMap, Handler handler) {
return mSkDestroyListener;
diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
index d41550b..7a5895f 100644
--- a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
+++ b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
@@ -130,6 +130,7 @@
public void setUp() throws Exception {
mExecutor = Executors.newSingleThreadExecutor();
mOtCtl = new OtDaemonController();
+ mController.setEnabledAndWait(true);
mController.leaveAndWait();
// TODO: b/323301831 - This is a workaround to avoid unnecessary delay to re-form a network