Add callbacks for service offload
Components that can provide offload like IpClient (packet
filter offloading) can use the API to register a callback to be notified
when offload is necessary.
Bug: 269240366
Test: atest CtsNetTestCases
Change-Id: I8080702f5b530001b88e79e504f4722ac01bc576
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index ffa2857..9520ef6 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -82,6 +82,17 @@
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
+// The filegroup lists files that are necessary for verifying building mdns as a standalone,
+// for use with service-connectivity-mdns-standalone-build-test
+filegroup {
+ name: "framework-connectivity-t-mdns-standalone-build-sources",
+ srcs: [
+ "src/android/net/nsd/OffloadEngine.java",
+ "src/android/net/nsd/OffloadServiceInfo.java",
+ ],
+ visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
+
java_library {
name: "framework-connectivity-t-pre-jarjar",
defaults: ["framework-connectivity-t-defaults"],
diff --git a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
index 5533154..e671db1 100644
--- a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
+++ b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
@@ -17,6 +17,7 @@
package android.net.nsd;
import android.net.nsd.INsdManagerCallback;
+import android.net.nsd.IOffloadEngine;
import android.net.nsd.NsdServiceInfo;
import android.os.Messenger;
@@ -35,4 +36,6 @@
void stopResolution(int listenerKey);
void registerServiceInfoCallback(int listenerKey, in NsdServiceInfo serviceInfo);
void unregisterServiceInfoCallback(int listenerKey);
+ void registerOffloadEngine(String ifaceName, in IOffloadEngine cb, long offloadCapabilities, long offloadType);
+ void unregisterOffloadEngine(in IOffloadEngine cb);
}
\ No newline at end of file
diff --git a/framework-t/src/android/net/nsd/IOffloadEngine.aidl b/framework-t/src/android/net/nsd/IOffloadEngine.aidl
new file mode 100644
index 0000000..379c2e0
--- /dev/null
+++ b/framework-t/src/android/net/nsd/IOffloadEngine.aidl
@@ -0,0 +1,28 @@
+/**
+ * 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 android.net.nsd;
+
+import android.net.nsd.OffloadServiceInfo;
+
+/**
+ * Callbacks from NsdService to inform providers of packet offload.
+ * @hide
+ */
+oneway interface IOffloadEngine {
+ void onOffloadServiceUpdated(in OffloadServiceInfo info);
+ void onOffloadServiceRemoved(in OffloadServiceInfo info);
+}
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index 2930cbd..934f185 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -16,6 +16,9 @@
package android.net.nsd;
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.NETWORK_STACK;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_PLATFORM_MDNS_BACKEND;
import static android.net.connectivity.ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER;
@@ -45,9 +48,11 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.CollectionUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -246,6 +251,10 @@
public static final int UNREGISTER_SERVICE_CALLBACK = 31;
/** @hide */
public static final int UNREGISTER_SERVICE_CALLBACK_SUCCEEDED = 32;
+ /** @hide */
+ public static final int REGISTER_OFFLOAD_ENGINE = 33;
+ /** @hide */
+ public static final int UNREGISTER_OFFLOAD_ENGINE = 34;
/** Dns based service discovery protocol */
public static final int PROTOCOL_DNS_SD = 0x0001;
@@ -313,8 +322,107 @@
private final ArrayMap<Integer, PerNetworkDiscoveryTracker>
mPerNetworkDiscoveryMap = new ArrayMap<>();
+ @GuardedBy("mOffloadEngines")
+ private final ArrayList<OffloadEngineProxy> mOffloadEngines = new ArrayList<>();
private final ServiceHandler mHandler;
+ private static class OffloadEngineProxy extends IOffloadEngine.Stub {
+ private final Executor mExecutor;
+ private final OffloadEngine mEngine;
+
+ private OffloadEngineProxy(@NonNull Executor executor, @NonNull OffloadEngine appCb) {
+ mExecutor = executor;
+ mEngine = appCb;
+ }
+
+ @Override
+ public void onOffloadServiceUpdated(OffloadServiceInfo info) {
+ mExecutor.execute(() -> mEngine.onOffloadServiceUpdated(info));
+ }
+
+ @Override
+ public void onOffloadServiceRemoved(OffloadServiceInfo info) {
+ mExecutor.execute(() -> mEngine.onOffloadServiceRemoved(info));
+ }
+ }
+
+ /**
+ * Registers an OffloadEngine with NsdManager.
+ *
+ * A caller can register itself as an OffloadEngine if it supports mDns hardware offload.
+ * The caller must implement the {@link OffloadEngine} interface and update hardware offload
+ * state property when the {@link OffloadEngine#onOffloadServiceUpdated} and
+ * {@link OffloadEngine#onOffloadServiceRemoved} callback are called. Multiple engines may be
+ * registered for the same interface, and that the same engine cannot be registered twice.
+ *
+ * @param ifaceName indicates which network interface the hardware offload runs on
+ * @param offloadType the type of offload that the offload engine support
+ * @param offloadCapability the capabilities of the offload engine
+ * @param executor the executor on which to receive the offload callbacks
+ * @param engine the OffloadEngine that will receive the offload callbacks
+ * @throws IllegalStateException if the engine is already registered.
+ *
+ * @hide
+ */
+ //@SystemApi
+ @RequiresPermission(anyOf = {NETWORK_SETTINGS, PERMISSION_MAINLINE_NETWORK_STACK,
+ NETWORK_STACK})
+ public void registerOffloadEngine(@NonNull String ifaceName,
+ @OffloadEngine.OffloadType long offloadType,
+ @OffloadEngine.OffloadCapability long offloadCapability, @NonNull Executor executor,
+ @NonNull OffloadEngine engine) {
+ Objects.requireNonNull(ifaceName);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(engine);
+ final OffloadEngineProxy cbImpl = new OffloadEngineProxy(executor, engine);
+ synchronized (mOffloadEngines) {
+ if (CollectionUtils.contains(mOffloadEngines, impl -> impl.mEngine == engine)) {
+ throw new IllegalStateException("This engine is already registered");
+ }
+ mOffloadEngines.add(cbImpl);
+ }
+ try {
+ mService.registerOffloadEngine(ifaceName, cbImpl, offloadCapability, offloadType);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * Unregisters an OffloadEngine from NsdService.
+ *
+ * A caller can unregister itself as an OffloadEngine when it doesn't want to receive the
+ * callback anymore. The OffloadEngine must have been previously registered with the system
+ * using the {@link NsdManager#registerOffloadEngine} method.
+ *
+ * @param engine OffloadEngine object to be removed from NsdService
+ * @throws IllegalStateException if the engine is not registered.
+ *
+ * @hide
+ */
+ //@SystemApi
+ @RequiresPermission(anyOf = {NETWORK_SETTINGS, PERMISSION_MAINLINE_NETWORK_STACK,
+ NETWORK_STACK})
+ public void unregisterOffloadEngine(@NonNull OffloadEngine engine) {
+ Objects.requireNonNull(engine);
+ final OffloadEngineProxy cbImpl;
+ synchronized (mOffloadEngines) {
+ final int index = CollectionUtils.indexOf(mOffloadEngines,
+ impl -> impl.mEngine == engine);
+ if (index < 0) {
+ throw new IllegalStateException("This engine is not registered");
+ }
+ cbImpl = mOffloadEngines.remove(index);
+ }
+
+ try {
+ mService.unregisterOffloadEngine(cbImpl);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
private class PerNetworkDiscoveryTracker {
final String mServiceType;
final int mProtocolType;
diff --git a/framework-t/src/android/net/nsd/OffloadEngine.java b/framework-t/src/android/net/nsd/OffloadEngine.java
new file mode 100644
index 0000000..d939725
--- /dev/null
+++ b/framework-t/src/android/net/nsd/OffloadEngine.java
@@ -0,0 +1,85 @@
+/*
+ * 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 android.net.nsd;
+
+import android.annotation.LongDef;
+import android.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * OffloadEngine is an interface for mDns hardware offloading.
+ *
+ * An offloading engine can interact with the firmware code to instruct the hardware to
+ * offload some of mDns network traffic before it reached android OS. This can improve the
+ * power consumption performance of the host system by not always waking up the OS to handle
+ * the mDns packet when the device is in low power mode.
+ *
+ * @hide
+ */
+//@SystemApi
+public interface OffloadEngine {
+ /**
+ * Indicates that the OffloadEngine can generate replies to mDns queries.
+ *
+ * @see OffloadServiceInfo#getOffloadPayload()
+ */
+ int OFFLOAD_TYPE_REPLY = 1;
+ /**
+ * Indicates that the OffloadEngine can filter and drop mDns queries.
+ */
+ int OFFLOAD_TYPE_FILTER_QUERIES = 1 << 1;
+ /**
+ * Indicates that the OffloadEngine can filter and drop mDns replies. It can allow mDns packets
+ * to be received even when no app holds a {@link android.net.wifi.WifiManager.MulticastLock}.
+ */
+ int OFFLOAD_TYPE_FILTER_REPLIES = 1 << 2;
+
+ /**
+ * Indicates that the OffloadEngine can bypass multicast lock.
+ */
+ int OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK = 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @LongDef(flag = true, prefix = {"OFFLOAD_TYPE"}, value = {
+ OFFLOAD_TYPE_REPLY,
+ OFFLOAD_TYPE_FILTER_QUERIES,
+ OFFLOAD_TYPE_FILTER_REPLIES,
+ })
+ @interface OffloadType {}
+
+ @Retention(RetentionPolicy.SOURCE)
+ @LongDef(flag = true, prefix = {"OFFLOAD_CAPABILITY"}, value = {
+ OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK
+ })
+ @interface OffloadCapability {}
+
+ /**
+ * To be called when the OffloadServiceInfo is added or updated.
+ *
+ * @param info The OffloadServiceInfo to add or update.
+ */
+ void onOffloadServiceUpdated(@NonNull OffloadServiceInfo info);
+
+ /**
+ * To be called when the OffloadServiceInfo is removed.
+ *
+ * @param info The OffloadServiceInfo to remove.
+ */
+ void onOffloadServiceRemoved(@NonNull OffloadServiceInfo info);
+}
diff --git a/framework-t/src/android/net/nsd/OffloadServiceInfo.java b/framework-t/src/android/net/nsd/OffloadServiceInfo.java
new file mode 100644
index 0000000..4aec720
--- /dev/null
+++ b/framework-t/src/android/net/nsd/OffloadServiceInfo.java
@@ -0,0 +1,313 @@
+/*
+ * 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 android.net.nsd;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.net.module.util.HexDump;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The OffloadServiceInfo class contains all the necessary information the OffloadEngine needs to
+ * know about how to offload an mDns service. The OffloadServiceInfo is keyed on
+ * {@link OffloadServiceInfo.Key} which is a (serviceName, serviceType) pair.
+ *
+ * @hide
+ */
+// @SystemApi
+public final class OffloadServiceInfo implements Parcelable {
+ @NonNull
+ private final Key mKey;
+ @NonNull
+ private final String mHostname;
+ @NonNull final List<String> mSubtypes;
+ @Nullable
+ private final byte[] mOffloadPayload;
+ private final int mPriority;
+ private final long mOffloadType;
+
+ /**
+ * Creates a new OffloadServiceInfo object with the specified parameters.
+ *
+ * @param key The key of the service.
+ * @param subtypes The list of subTypes of the service.
+ * @param hostname The name of the host offering the service. It is meaningful only when
+ * offloadType contains OFFLOAD_REPLY.
+ * @param offloadPayload The raw udp payload for hardware offloading.
+ * @param priority The priority of the service, @see #getPriority.
+ * @param offloadType The type of the service offload, @see #getOffloadType.
+ */
+ public OffloadServiceInfo(@NonNull Key key,
+ @NonNull List<String> subtypes, @NonNull String hostname,
+ @Nullable byte[] offloadPayload,
+ @IntRange(from = 0, to = Integer.MAX_VALUE) int priority,
+ @OffloadEngine.OffloadType long offloadType) {
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(subtypes);
+ Objects.requireNonNull(hostname);
+ mKey = key;
+ mSubtypes = subtypes;
+ mHostname = hostname;
+ mOffloadPayload = offloadPayload;
+ mPriority = priority;
+ mOffloadType = offloadType;
+ }
+
+ /**
+ * Creates a new OffloadServiceInfo object from a Parcel.
+ *
+ * @param in The Parcel to read the object from.
+ *
+ * @hide
+ */
+ public OffloadServiceInfo(@NonNull Parcel in) {
+ mKey = in.readParcelable(Key.class.getClassLoader(),
+ Key.class);
+ mSubtypes = in.createStringArrayList();
+ mHostname = in.readString();
+ mOffloadPayload = in.createByteArray();
+ mPriority = in.readInt();
+ mOffloadType = in.readLong();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mKey, flags);
+ dest.writeStringList(mSubtypes);
+ dest.writeString(mHostname);
+ dest.writeByteArray(mOffloadPayload);
+ dest.writeInt(mPriority);
+ dest.writeLong(mOffloadType);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator<OffloadServiceInfo> CREATOR = new Creator<OffloadServiceInfo>() {
+ @Override
+ public OffloadServiceInfo createFromParcel(Parcel in) {
+ return new OffloadServiceInfo(in);
+ }
+
+ @Override
+ public OffloadServiceInfo[] newArray(int size) {
+ return new OffloadServiceInfo[size];
+ }
+ };
+
+ /**
+ * Get the {@link Key}.
+ */
+ @NonNull
+ public Key getKey() {
+ return mKey;
+ }
+
+ /**
+ * Get the host name. (e.g. "Android.local" )
+ */
+ @NonNull
+ public String getHostname() {
+ return mHostname;
+ }
+
+ /**
+ * Get the service subtypes. (e.g. ["_ann"] )
+ */
+ @NonNull
+ public List<String> getSubtypes() {
+ return Collections.unmodifiableList(mSubtypes);
+ }
+
+ /**
+ * Get the raw udp payload that the OffloadEngine can use to directly reply the incoming query.
+ * <p>
+ * It is null if the OffloadEngine can not handle transmit. The packet must be sent as-is when
+ * replying to query.
+ */
+ @Nullable
+ public byte[] getOffloadPayload() {
+ if (mOffloadPayload == null) {
+ return null;
+ } else {
+ return mOffloadPayload.clone();
+ }
+ }
+
+ /**
+ * Get the offloadType.
+ * <p>
+ * For example, if the {@link com.android.server.NsdService} requests the OffloadEngine to both
+ * filter the mDNS queries and replies, the {@link #mOffloadType} =
+ * ({@link OffloadEngine#OFFLOAD_TYPE_FILTER_QUERIES} |
+ * {@link OffloadEngine#OFFLOAD_TYPE_FILTER_REPLIES}).
+ */
+ @OffloadEngine.OffloadType public long getOffloadType() {
+ return mOffloadType;
+ }
+
+ /**
+ * Get the priority for the OffloadServiceInfo.
+ * <p>
+ * When OffloadEngine don't have enough resource
+ * (e.g. not enough memory) to offload all the OffloadServiceInfo. The OffloadServiceInfo
+ * having lower priority values should be handled by the OffloadEngine first.
+ */
+ public int getPriority() {
+ return mPriority;
+ }
+
+ /**
+ * Only for debug purpose, the string can be long as the raw packet is dump in the string.
+ */
+ @Override
+ public String toString() {
+ return String.format(
+ "OffloadServiceInfo{ mOffloadServiceInfoKey=%s, mHostName=%s, "
+ + "mOffloadPayload=%s, mPriority=%d, mOffloadType=%d, mSubTypes=%s }",
+ mKey,
+ mHostname, HexDump.dumpHexString(mOffloadPayload), mPriority,
+ mOffloadType, mSubtypes.toString());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof OffloadServiceInfo)) return false;
+ OffloadServiceInfo that = (OffloadServiceInfo) o;
+ return mPriority == that.mPriority && mOffloadType == that.mOffloadType
+ && mKey.equals(that.mKey)
+ && mHostname.equals(
+ that.mHostname) && Arrays.equals(mOffloadPayload,
+ that.mOffloadPayload)
+ && mSubtypes.equals(that.mSubtypes);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(mKey, mHostname, mPriority,
+ mOffloadType, mSubtypes);
+ result = 31 * result + Arrays.hashCode(mOffloadPayload);
+ return result;
+ }
+
+ /**
+ * The {@link OffloadServiceInfo.Key} is the (serviceName, serviceType) pair.
+ */
+ public static final class Key implements Parcelable {
+ @NonNull
+ private final String mServiceName;
+ @NonNull
+ private final String mServiceType;
+
+ /**
+ * Creates a new OffloadServiceInfoKey object with the specified parameters.
+ *
+ * @param serviceName The name of the service.
+ * @param serviceType The type of the service.
+ */
+ public Key(@NonNull String serviceName, @NonNull String serviceType) {
+ Objects.requireNonNull(serviceName);
+ Objects.requireNonNull(serviceType);
+ mServiceName = serviceName;
+ mServiceType = serviceType;
+ }
+
+ /**
+ * Creates a new OffloadServiceInfoKey object from a Parcel.
+ *
+ * @param in The Parcel to read the object from.
+ *
+ * @hide
+ */
+ public Key(@NonNull Parcel in) {
+ mServiceName = in.readString();
+ mServiceType = in.readString();
+ }
+ /**
+ * Get the service name. (e.g. "NsdChat")
+ */
+ @NonNull
+ public String getServiceName() {
+ return mServiceName;
+ }
+
+ /**
+ * Get the service type. (e.g. "_http._tcp.local" )
+ */
+ @NonNull
+ public String getServiceType() {
+ return mServiceType;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mServiceName);
+ dest.writeString(mServiceType);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator<Key> CREATOR =
+ new Creator<Key>() {
+ @Override
+ public Key createFromParcel(Parcel in) {
+ return new Key(in);
+ }
+
+ @Override
+ public Key[] newArray(int size) {
+ return new Key[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Key)) return false;
+ Key that = (Key) o;
+ return Objects.equals(mServiceName, that.mServiceName) && Objects.equals(
+ mServiceType, that.mServiceType);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mServiceName, mServiceType);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("OffloadServiceInfoKey{ mServiceName=%s, mServiceType=%s }",
+ mServiceName, mServiceType);
+ }
+ }
+}