Merge "Prepare exposing LocalNetworkConfig and its satellites" into main
diff --git a/Tethering/common/TetheringLib/api/lint-baseline.txt b/Tethering/common/TetheringLib/api/lint-baseline.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tethering/common/TetheringLib/api/lint-baseline.txt
diff --git a/Tethering/common/TetheringLib/api/module-lib-lint-baseline.txt b/Tethering/common/TetheringLib/api/module-lib-lint-baseline.txt
new file mode 100644
index 0000000..1d09598
--- /dev/null
+++ b/Tethering/common/TetheringLib/api/module-lib-lint-baseline.txt
@@ -0,0 +1,23 @@
+// Baseline format: 1.0
+BroadcastBehavior: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED:
+ Field 'ACTION_TETHER_STATE_CHANGED' is missing @BroadcastBehavior
+
+
+RequiresPermission: android.net.TetheringManager#requestLatestTetheringEntitlementResult(int, boolean, java.util.concurrent.Executor, android.net.TetheringManager.OnTetheringEntitlementResultListener):
+ Method 'requestLatestTetheringEntitlementResult' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.TetheringManager#startTethering(android.net.TetheringManager.TetheringRequest, java.util.concurrent.Executor, android.net.TetheringManager.StartTetheringCallback):
+ Method 'startTethering' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.TetheringManager#startTethering(int, java.util.concurrent.Executor, android.net.TetheringManager.StartTetheringCallback):
+ Method 'startTethering' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.TetheringManager#stopAllTethering():
+ Method 'stopAllTethering' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.TetheringManager#stopTethering(int):
+ Method 'stopTethering' documentation mentions permissions already declared by @RequiresPermission
+
+
+SdkConstant: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED:
+ Field 'ACTION_TETHER_STATE_CHANGED' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+
+
+Todo: android.net.TetheringConstants:
+ Documentation mentions 'TODO'
diff --git a/Tethering/common/TetheringLib/api/system-lint-baseline.txt b/Tethering/common/TetheringLib/api/system-lint-baseline.txt
new file mode 100644
index 0000000..e678ce1
--- /dev/null
+++ b/Tethering/common/TetheringLib/api/system-lint-baseline.txt
@@ -0,0 +1,17 @@
+// Baseline format: 1.0
+BroadcastBehavior: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED:
+ Field 'ACTION_TETHER_STATE_CHANGED' is missing @BroadcastBehavior
+
+
+RequiresPermission: android.net.TetheringManager#requestLatestTetheringEntitlementResult(int, boolean, java.util.concurrent.Executor, android.net.TetheringManager.OnTetheringEntitlementResultListener):
+ Method 'requestLatestTetheringEntitlementResult' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.TetheringManager#startTethering(android.net.TetheringManager.TetheringRequest, java.util.concurrent.Executor, android.net.TetheringManager.StartTetheringCallback):
+ Method 'startTethering' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.TetheringManager#stopAllTethering():
+ Method 'stopAllTethering' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.TetheringManager#stopTethering(int):
+ Method 'stopTethering' documentation mentions permissions already declared by @RequiresPermission
+
+
+SdkConstant: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED:
+ Field 'ACTION_TETHER_STATE_CHANGED' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 2b14a42..4bd7e6a 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -1597,10 +1597,8 @@
@Override
public int hashCode() {
- // TODO: if this is ever used in production code, don't pass ifindices
- // to Objects.hash() to avoid autoboxing overhead.
- return Objects.hash(upstreamIfindex, downstreamIfindex, sourcePrefix, inDstMac,
- outSrcMac, outDstMac);
+ return 13 * upstreamIfindex + 41 * downstreamIfindex
+ + Objects.hash(sourcePrefix, inDstMac, outSrcMac, outDstMac);
}
@Override
@@ -1725,9 +1723,8 @@
@Override
public int hashCode() {
- // TODO: if this is ever used in production code, don't pass ifindices
- // to Objects.hash() to avoid autoboxing overhead.
- return Objects.hash(upstreamIfindex, downstreamIfindex, address, srcMac, dstMac);
+ return 13 * upstreamIfindex + 41 * downstreamIfindex
+ + Objects.hash(address, srcMac, dstMac);
}
@Override
diff --git a/framework-t/api/module-lib-lint-baseline.txt b/framework-t/api/module-lib-lint-baseline.txt
index 3158bd4..6f954df 100644
--- a/framework-t/api/module-lib-lint-baseline.txt
+++ b/framework-t/api/module-lib-lint-baseline.txt
@@ -5,3 +5,17 @@
Methods must not mention RuntimeException subclasses in throws clauses (was `java.lang.SecurityException`)
BannedThrow: android.app.usage.NetworkStatsManager#queryTaggedSummary(android.net.NetworkTemplate, long, long):
Methods must not mention RuntimeException subclasses in throws clauses (was `java.lang.SecurityException`)
+
+
+MissingPermission: android.net.IpSecManager#startTunnelModeTransformMigration(android.net.IpSecTransform, java.net.InetAddress, java.net.InetAddress):
+ Feature field FEATURE_IPSEC_TUNNEL_MIGRATION required by method android.net.IpSecManager.startTunnelModeTransformMigration(android.net.IpSecTransform, java.net.InetAddress, java.net.InetAddress) is hidden or removed
+
+
+RequiresPermission: android.app.usage.NetworkStatsManager#registerUsageCallback(android.net.NetworkTemplate, long, java.util.concurrent.Executor, android.app.usage.NetworkStatsManager.UsageCallback):
+ Method 'registerUsageCallback' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.EthernetManager#disableInterface(String, java.util.concurrent.Executor, android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>):
+ Method 'disableInterface' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.EthernetManager#enableInterface(String, java.util.concurrent.Executor, android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>):
+ Method 'enableInterface' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.EthernetManager#updateConfiguration(String, android.net.EthernetNetworkUpdateRequest, java.util.concurrent.Executor, android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>):
+ Method 'updateConfiguration' documentation mentions permissions already declared by @RequiresPermission
diff --git a/framework-t/api/system-lint-baseline.txt b/framework-t/api/system-lint-baseline.txt
index c6055f6..4f7af87 100644
--- a/framework-t/api/system-lint-baseline.txt
+++ b/framework-t/api/system-lint-baseline.txt
@@ -7,6 +7,18 @@
Methods must not throw generic exceptions (`java.lang.Throwable`)
+MissingPermission: android.net.IpSecManager#startTunnelModeTransformMigration(android.net.IpSecTransform, java.net.InetAddress, java.net.InetAddress):
+ Feature field FEATURE_IPSEC_TUNNEL_MIGRATION required by method android.net.IpSecManager.startTunnelModeTransformMigration(android.net.IpSecTransform, java.net.InetAddress, java.net.InetAddress) is hidden or removed
+
+
+RequiresPermission: android.net.EthernetManager#disableInterface(String, java.util.concurrent.Executor, android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>):
+ Method 'disableInterface' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.EthernetManager#enableInterface(String, java.util.concurrent.Executor, android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>):
+ Method 'enableInterface' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.EthernetManager#updateConfiguration(String, android.net.EthernetNetworkUpdateRequest, java.util.concurrent.Executor, android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>):
+ Method 'updateConfiguration' documentation mentions permissions already declared by @RequiresPermission
+
+
UnflaggedApi: android.nearby.CredentialElement#equals(Object):
New API must be flagged with @FlaggedApi: method android.nearby.CredentialElement.equals(Object)
UnflaggedApi: android.nearby.CredentialElement#hashCode():
diff --git a/framework/api/lint-baseline.txt b/framework/api/lint-baseline.txt
index 2f4004a..4465bcb 100644
--- a/framework/api/lint-baseline.txt
+++ b/framework/api/lint-baseline.txt
@@ -1,4 +1,19 @@
// Baseline format: 1.0
+BroadcastBehavior: android.net.ConnectivityManager#ACTION_BACKGROUND_DATA_SETTING_CHANGED:
+ Field 'ACTION_BACKGROUND_DATA_SETTING_CHANGED' is missing @BroadcastBehavior
+
+
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.app.PendingIntent):
+ Method 'requestNetwork' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback):
+ Method 'requestNetwork' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities#getOwnerUid():
+ Method 'getOwnerUid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.http.BidirectionalStream.Builder#setTrafficStatsUid(int):
+ Method 'setTrafficStatsUid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.http.UrlRequest.Builder#setTrafficStatsUid(int):
+ Method 'setTrafficStatsUid' documentation mentions permissions without declaring @RequiresPermission
+
+
VisiblySynchronized: android.net.NetworkInfo#toString():
Internal locks must not be exposed (synchronizing on this or class is still
- externally observable): method android.net.NetworkInfo.toString()
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index bfb4981..026d8a9 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -235,6 +235,7 @@
public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
ctor @Deprecated public VpnTransportInfo(int, @Nullable String);
+ method public long getApplicableRedactions();
method @Nullable public String getSessionId();
method @NonNull public android.net.VpnTransportInfo makeCopy(long);
}
diff --git a/framework/api/module-lib-lint-baseline.txt b/framework/api/module-lib-lint-baseline.txt
new file mode 100644
index 0000000..53a8c5e
--- /dev/null
+++ b/framework/api/module-lib-lint-baseline.txt
@@ -0,0 +1,33 @@
+// Baseline format: 1.0
+BroadcastBehavior: android.net.ConnectivityManager#ACTION_BACKGROUND_DATA_SETTING_CHANGED:
+ Field 'ACTION_BACKGROUND_DATA_SETTING_CHANGED' is missing @BroadcastBehavior
+
+
+RequiresPermission: android.net.ConnectivityManager#isTetheringSupported():
+ Method 'isTetheringSupported' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.app.PendingIntent):
+ Method 'requestNetwork' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback):
+ Method 'requestNetwork' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.ConnectivityManager#requestRouteToHostAddress(int, java.net.InetAddress):
+ Method 'requestRouteToHostAddress' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.LinkProperties#getCaptivePortalApiUrl():
+ Method 'getCaptivePortalApiUrl' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.LinkProperties#getCaptivePortalData():
+ Method 'getCaptivePortalData' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities#getOwnerUid():
+ Method 'getOwnerUid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities#getUnderlyingNetworks():
+ Method 'getUnderlyingNetworks' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities.Builder#setAllowedUids(java.util.Set<java.lang.Integer>):
+ Method 'setAllowedUids' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities.Builder#setSignalStrength(int):
+ Method 'setSignalStrength' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities.Builder#setUnderlyingNetworks(java.util.List<android.net.Network>):
+ Method 'setUnderlyingNetworks' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.NetworkRequest.Builder#setSignalStrength(int):
+ Method 'setSignalStrength' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.http.BidirectionalStream.Builder#setTrafficStatsUid(int):
+ Method 'setTrafficStatsUid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.http.UrlRequest.Builder#setTrafficStatsUid(int):
+ Method 'setTrafficStatsUid' documentation mentions permissions without declaring @RequiresPermission
diff --git a/framework/api/system-lint-baseline.txt b/framework/api/system-lint-baseline.txt
index 9a97707..3ac97c0 100644
--- a/framework/api/system-lint-baseline.txt
+++ b/framework/api/system-lint-baseline.txt
@@ -1 +1,29 @@
// Baseline format: 1.0
+BroadcastBehavior: android.net.ConnectivityManager#ACTION_BACKGROUND_DATA_SETTING_CHANGED:
+ Field 'ACTION_BACKGROUND_DATA_SETTING_CHANGED' is missing @BroadcastBehavior
+
+
+RequiresPermission: android.net.ConnectivityManager#isTetheringSupported():
+ Method 'isTetheringSupported' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.app.PendingIntent):
+ Method 'requestNetwork' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback):
+ Method 'requestNetwork' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.LinkProperties#getCaptivePortalApiUrl():
+ Method 'getCaptivePortalApiUrl' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.LinkProperties#getCaptivePortalData():
+ Method 'getCaptivePortalData' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities#getOwnerUid():
+ Method 'getOwnerUid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities#getUnderlyingNetworks():
+ Method 'getUnderlyingNetworks' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities.Builder#setSignalStrength(int):
+ Method 'setSignalStrength' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities.Builder#setUnderlyingNetworks(java.util.List<android.net.Network>):
+ Method 'setUnderlyingNetworks' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.NetworkRequest.Builder#setSignalStrength(int):
+ Method 'setSignalStrength' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.http.BidirectionalStream.Builder#setTrafficStatsUid(int):
+ Method 'setTrafficStatsUid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.http.UrlRequest.Builder#setTrafficStatsUid(int):
+ Method 'setTrafficStatsUid' documentation mentions permissions without declaring @RequiresPermission
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index c74f229..43357e4 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -1647,10 +1647,12 @@
mContext, MdnsFeatureFlags.INCLUDE_INET_ADDRESS_RECORDS_IN_PROBING))
.setIsExpiredServicesRemovalEnabled(mDeps.isTrunkStableFeatureEnabled(
MdnsFeatureFlags.NSD_EXPIRED_SERVICES_REMOVAL))
+ .setIsLabelCountLimitEnabled(mDeps.isTetheringFeatureNotChickenedOut(
+ mContext, MdnsFeatureFlags.NSD_LIMIT_LABEL_COUNT))
.build();
mMdnsSocketClient =
new MdnsMultinetworkSocketClient(handler.getLooper(), mMdnsSocketProvider,
- LOGGER.forSubComponent("MdnsMultinetworkSocketClient"));
+ LOGGER.forSubComponent("MdnsMultinetworkSocketClient"), flags);
mMdnsDiscoveryManager = deps.makeMdnsDiscoveryManager(new ExecutorProvider(),
mMdnsSocketClient, LOGGER.forSubComponent("MdnsDiscoveryManager"), flags);
handler.post(() -> mMdnsSocketClient.setCallback(mMdnsDiscoveryManager));
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
index 6f7645e..738c151 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
@@ -36,6 +36,11 @@
public static final String NSD_EXPIRED_SERVICES_REMOVAL =
"nsd_expired_services_removal";
+ /**
+ * A feature flag to control whether the label count limit should be enabled.
+ */
+ public static final String NSD_LIMIT_LABEL_COUNT = "nsd_limit_label_count";
+
// Flag for offload feature
public final boolean mIsMdnsOffloadFeatureEnabled;
@@ -45,14 +50,20 @@
// Flag for expired services removal
public final boolean mIsExpiredServicesRemovalEnabled;
+ // Flag for label count limit
+ public final boolean mIsLabelCountLimitEnabled;
+
/**
* The constructor for {@link MdnsFeatureFlags}.
*/
public MdnsFeatureFlags(boolean isOffloadFeatureEnabled,
- boolean includeInetAddressRecordsInProbing, boolean isExpiredServicesRemovalEnabled) {
+ boolean includeInetAddressRecordsInProbing,
+ boolean isExpiredServicesRemovalEnabled,
+ boolean isLabelCountLimitEnabled) {
mIsMdnsOffloadFeatureEnabled = isOffloadFeatureEnabled;
mIncludeInetAddressRecordsInProbing = includeInetAddressRecordsInProbing;
mIsExpiredServicesRemovalEnabled = isExpiredServicesRemovalEnabled;
+ mIsLabelCountLimitEnabled = isLabelCountLimitEnabled;
}
@@ -67,6 +78,7 @@
private boolean mIsMdnsOffloadFeatureEnabled;
private boolean mIncludeInetAddressRecordsInProbing;
private boolean mIsExpiredServicesRemovalEnabled;
+ private boolean mIsLabelCountLimitEnabled;
/**
* The constructor for {@link Builder}.
@@ -75,6 +87,7 @@
mIsMdnsOffloadFeatureEnabled = false;
mIncludeInetAddressRecordsInProbing = false;
mIsExpiredServicesRemovalEnabled = true; // Default enabled.
+ mIsLabelCountLimitEnabled = true; // Default enabled.
}
/**
@@ -109,11 +122,23 @@
}
/**
+ * Set whether the label count limit is enabled.
+ *
+ * @see #NSD_LIMIT_LABEL_COUNT
+ */
+ public Builder setIsLabelCountLimitEnabled(boolean isLabelCountLimitEnabled) {
+ mIsLabelCountLimitEnabled = isLabelCountLimitEnabled;
+ return this;
+ }
+
+ /**
* Builds a {@link MdnsFeatureFlags} with the arguments supplied to this builder.
*/
public MdnsFeatureFlags build() {
return new MdnsFeatureFlags(mIsMdnsOffloadFeatureEnabled,
- mIncludeInetAddressRecordsInProbing, mIsExpiredServicesRemovalEnabled);
+ mIncludeInetAddressRecordsInProbing,
+ mIsExpiredServicesRemovalEnabled,
+ mIsLabelCountLimitEnabled);
}
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
index 42a6b0d..62c37ad 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -65,11 +65,12 @@
private final MdnsProber mProber;
@NonNull
private final MdnsReplySender mReplySender;
-
@NonNull
private final SharedLog mSharedLog;
@NonNull
private final byte[] mPacketCreationBuffer;
+ @NonNull
+ private final MdnsFeatureFlags mMdnsFeatureFlags;
/**
* Callbacks called by {@link MdnsInterfaceAdvertiser} to report status updates.
@@ -213,6 +214,7 @@
mProber = deps.makeMdnsProber(sharedLog.getTag(), looper, mReplySender, mProbingCallback,
sharedLog);
mSharedLog = sharedLog;
+ mMdnsFeatureFlags = mdnsFeatureFlags;
}
/**
@@ -351,7 +353,7 @@
public void handlePacket(byte[] recvbuf, int length, InetSocketAddress src) {
final MdnsPacket packet;
try {
- packet = MdnsPacket.parse(new MdnsPacketReader(recvbuf, length));
+ packet = MdnsPacket.parse(new MdnsPacketReader(recvbuf, length, mMdnsFeatureFlags));
} catch (MdnsPacket.ParseException e) {
mSharedLog.e("Error parsing mDNS packet", e);
if (DBG) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
index 4ba6912..e7b0eaa 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
@@ -50,6 +50,7 @@
@NonNull private final Handler mHandler;
@NonNull private final MdnsSocketProvider mSocketProvider;
@NonNull private final SharedLog mSharedLog;
+ @NonNull private final MdnsFeatureFlags mMdnsFeatureFlags;
private final ArrayMap<MdnsServiceBrowserListener, InterfaceSocketCallback> mSocketRequests =
new ArrayMap<>();
@@ -58,11 +59,12 @@
private int mReceivedPacketNumber = 0;
public MdnsMultinetworkSocketClient(@NonNull Looper looper,
- @NonNull MdnsSocketProvider provider,
- @NonNull SharedLog sharedLog) {
+ @NonNull MdnsSocketProvider provider, @NonNull SharedLog sharedLog,
+ @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
mHandler = new Handler(looper);
mSocketProvider = provider;
mSharedLog = sharedLog;
+ mMdnsFeatureFlags = mdnsFeatureFlags;
}
private class InterfaceSocketCallback implements MdnsSocketProvider.SocketCallback {
@@ -239,7 +241,7 @@
final MdnsPacket response;
try {
- response = MdnsResponseDecoder.parseResponse(recvbuf, length);
+ response = MdnsResponseDecoder.parseResponse(recvbuf, length, mMdnsFeatureFlags);
} catch (MdnsPacket.ParseException e) {
if (e.code != MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE) {
mSharedLog.e(e.getMessage(), e);
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketReader.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketReader.java
index aa38844..4917188 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketReader.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketReader.java
@@ -16,6 +16,7 @@
package com.android.server.connectivity.mdns;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.SparseArray;
@@ -33,21 +34,23 @@
private final byte[] buf;
private final int count;
private final SparseArray<LabelEntry> labelDictionary;
+ private final MdnsFeatureFlags mMdnsFeatureFlags;
private int pos;
private int limit;
/** Constructs a reader for the given packet. */
public MdnsPacketReader(DatagramPacket packet) {
- this(packet.getData(), packet.getLength());
+ this(packet.getData(), packet.getLength(), MdnsFeatureFlags.newBuilder().build());
}
/** Constructs a reader for the given packet. */
- public MdnsPacketReader(byte[] buffer, int length) {
+ public MdnsPacketReader(byte[] buffer, int length, @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
buf = buffer;
count = length;
pos = 0;
limit = -1;
labelDictionary = new SparseArray<>(16);
+ mMdnsFeatureFlags = mdnsFeatureFlags;
}
/**
@@ -269,4 +272,4 @@
this.label = label;
}
}
-}
\ No newline at end of file
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
index 050913f..b812bb4 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
@@ -84,9 +84,9 @@
* @throws MdnsPacket.ParseException if a response packet could not be parsed.
*/
@NonNull
- public static MdnsPacket parseResponse(@NonNull byte[] recvbuf, int length)
- throws MdnsPacket.ParseException {
- MdnsPacketReader reader = new MdnsPacketReader(recvbuf, length);
+ public static MdnsPacket parseResponse(@NonNull byte[] recvbuf, int length,
+ @NonNull MdnsFeatureFlags mdnsFeatureFlags) throws MdnsPacket.ParseException {
+ final MdnsPacketReader reader = new MdnsPacketReader(recvbuf, length, mdnsFeatureFlags);
final MdnsPacket mdnsPacket;
try {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
index d18a19b..82c8c5b 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
@@ -105,9 +105,10 @@
private AtomicInteger packetsCount;
@Nullable private Timer checkMulticastResponseTimer;
private final SharedLog sharedLog;
+ @NonNull private final MdnsFeatureFlags mdnsFeatureFlags;
public MdnsSocketClient(@NonNull Context context, @NonNull MulticastLock multicastLock,
- SharedLog sharedLog) {
+ SharedLog sharedLog, @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
this.sharedLog = sharedLog;
this.context = context;
this.multicastLock = multicastLock;
@@ -116,6 +117,7 @@
} else {
unicastReceiverBuffer = null;
}
+ this.mdnsFeatureFlags = mdnsFeatureFlags;
}
@Override
@@ -454,7 +456,8 @@
final MdnsPacket response;
try {
- response = MdnsResponseDecoder.parseResponse(packet.getData(), packet.getLength());
+ response = MdnsResponseDecoder.parseResponse(
+ packet.getData(), packet.getLength(), mdnsFeatureFlags);
} catch (MdnsPacket.ParseException e) {
sharedLog.w(String.format("Error while decoding %s packet (%d): %d",
responseType, packetNumber, e.code));
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 93a2602..ad9cfbe 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -45,8 +45,6 @@
import static android.system.OsConstants.ENODEV;
import static android.system.OsConstants.ENOENT;
import static android.system.OsConstants.EOPNOTSUPP;
-import static android.system.OsConstants.SOCK_RAW;
-import static android.system.OsConstants.SOCK_CLOEXEC;
import static com.android.server.ConnectivityStatsLog.NETWORK_BPF_MAP_INFO;
@@ -108,9 +106,6 @@
// Use legacy netd for releases before T.
private static boolean sInitialized = false;
- private static final String BPF_NET_MAPS_FORCE_DISABLE_JAVA_BPF_MAP =
- "bpf_net_maps_force_disable_java_bpf_map";
-
// Lock for sConfigurationMap entry for UID_RULES_CONFIGURATION_KEY.
// This entry is not accessed by others.
// BpfNetMaps acquires this lock while sequence of read, modify, and write.
@@ -330,19 +325,9 @@
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public int synchronizeKernelRCU() {
- // See p/m/C's staticlibs/native/bpf_headers/include/bpf/BpfUtils.h
- // for equivalent C implementation of this function.
try {
- // When closing socket, kernel calls synchronize_rcu()
- // from pf_key's sock_release().
- // Constants from //bionic/libc/include/sys/socket.h: AF_KEY=15
- // and kernel's include/uapi/linux/pfkeyv2.h: PF_KEY_V2=2
- Os.close(Os.socket(15 /*PF_KEY*/, SOCK_RAW | SOCK_CLOEXEC, 2));
+ BpfMap.synchronizeKernelRCU();
} catch (ErrnoException e) {
- // socket() can only fail due to lack of privs (selinux) or OOM,
- // close() always succeeds, but may return a pending error,
- // however on a freshly opened socket that cannot happen.
- // As such this failing is basically a build configuration error.
return -e.errno;
}
return 0;
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index 0bcb757..6325b46 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -96,12 +96,17 @@
srcs: [
"framework/**/DnsPacket.java",
"framework/**/DnsPacketUtils.java",
+ "framework/**/DnsSvcbPacket.java",
+ "framework/**/DnsSvcbRecord.java",
+ "framework/**/HexDump.java",
+ "framework/**/NetworkStackConstants.java",
],
sdk_version: "module_current",
visibility: [
"//packages/services/Iwlan:__subpackages__",
],
libs: [
+ "androidx.annotation_annotation",
"framework-annotations-lib",
"framework-connectivity.stubs.module_lib",
],
diff --git a/staticlibs/device/com/android/net/module/util/BpfMap.java b/staticlibs/device/com/android/net/module/util/BpfMap.java
index 595ac74..d622427 100644
--- a/staticlibs/device/com/android/net/module/util/BpfMap.java
+++ b/staticlibs/device/com/android/net/module/util/BpfMap.java
@@ -239,6 +239,11 @@
return Struct.parse(mValueClass, buffer);
}
+ /** Synchronize Kernel RCU */
+ public static void synchronizeKernelRCU() throws ErrnoException {
+ nativeSynchronizeKernelRCU();
+ }
+
private static native int nativeBpfFdGet(String path, int mode, int keySize, int valueSize)
throws ErrnoException, NullPointerException;
@@ -260,4 +265,6 @@
private native boolean nativeFindMapEntry(int fd, byte[] key, byte[] value)
throws ErrnoException;
+
+ private static native void nativeSynchronizeKernelRCU() throws ErrnoException;
}
diff --git a/staticlibs/framework/com/android/net/module/util/DnsPacket.java b/staticlibs/framework/com/android/net/module/util/DnsPacket.java
index 0dcdf1e..63106a1 100644
--- a/staticlibs/framework/com/android/net/module/util/DnsPacket.java
+++ b/staticlibs/framework/com/android/net/module/util/DnsPacket.java
@@ -56,6 +56,7 @@
*/
// TODO: Define the constant as a public constant in DnsResolver since it can never change.
private static final int TYPE_CNAME = 5;
+ public static final int TYPE_SVCB = 64;
/**
* Thrown when parsing packet failed.
@@ -282,7 +283,7 @@
* @param buf ByteBuffer input of record, must be in network byte order
* (which is the default).
*/
- private DnsRecord(@RecordType int rType, @NonNull ByteBuffer buf)
+ protected DnsRecord(@RecordType int rType, @NonNull ByteBuffer buf)
throws BufferUnderflowException, ParseException {
Objects.requireNonNull(buf);
this.rType = rType;
@@ -326,6 +327,8 @@
// Return a DnsRecord instance by default for backward compatibility, this is useful
// when a partner supports new type of DnsRecord but does not inherit DnsRecord.
switch (nsType) {
+ case TYPE_SVCB:
+ return new DnsSvcbRecord(rType, buf);
default:
return new DnsRecord(rType, buf);
}
diff --git a/staticlibs/framework/com/android/net/module/util/DnsSvcbPacket.java b/staticlibs/framework/com/android/net/module/util/DnsSvcbPacket.java
new file mode 100644
index 0000000..c7ed3e6
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/DnsSvcbPacket.java
@@ -0,0 +1,182 @@
+/*
+ * 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.net.module.util;
+
+import static android.net.DnsResolver.TYPE_A;
+import static android.net.DnsResolver.TYPE_AAAA;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.StringJoiner;
+
+/**
+ * A class for a DNS SVCB response packet.
+ *
+ * @hide
+ */
+public class DnsSvcbPacket extends DnsPacket {
+ public static final int TYPE_SVCB = 64;
+
+ private static final String TAG = DnsSvcbPacket.class.getSimpleName();
+
+ /**
+ * Creates a DnsSvcbPacket object from the given wire-format DNS packet.
+ */
+ private DnsSvcbPacket(@NonNull byte[] data) throws DnsPacket.ParseException {
+ // If data is null, ParseException will be thrown.
+ super(data);
+
+ final int questions = mHeader.getRecordCount(QDSECTION);
+ if (questions != 1) {
+ throw new DnsPacket.ParseException("Unexpected question count " + questions);
+ }
+ final int nsType = mRecords[QDSECTION].get(0).nsType;
+ if (nsType != TYPE_SVCB) {
+ throw new DnsPacket.ParseException("Unexpected query type " + nsType);
+ }
+ }
+
+ /**
+ * Returns true if the DnsSvcbPacket is a DNS response.
+ */
+ public boolean isResponse() {
+ return mHeader.isResponse();
+ }
+
+ /**
+ * Returns whether the given protocol alpn is supported.
+ */
+ public boolean isSupported(@NonNull String alpn) {
+ return findSvcbRecord(alpn) != null;
+ }
+
+ /**
+ * Returns the TargetName associated with the given protocol alpn.
+ * If the alpn is not supported, a null is returned.
+ */
+ @Nullable
+ public String getTargetName(@NonNull String alpn) {
+ final DnsSvcbRecord record = findSvcbRecord(alpn);
+ return (record != null) ? record.getTargetName() : null;
+ }
+
+ /**
+ * Returns the TargetName that associated with the given protocol alpn.
+ * If the alpn is not supported, -1 is returned.
+ */
+ public int getPort(@NonNull String alpn) {
+ final DnsSvcbRecord record = findSvcbRecord(alpn);
+ return (record != null) ? record.getPort() : -1;
+ }
+
+ /**
+ * Returns the IP addresses that support the given protocol alpn.
+ * If the alpn is not supported, an empty list is returned.
+ */
+ @NonNull
+ public List<InetAddress> getAddresses(@NonNull String alpn) {
+ final DnsSvcbRecord record = findSvcbRecord(alpn);
+ if (record == null) return Collections.EMPTY_LIST;
+
+ // As per draft-ietf-dnsop-svcb-https-10#section-7.4 and draft-ietf-add-ddr-10#section-4,
+ // if A/AAAA records are available in the Additional section, use the IP addresses in
+ // those records instead of the IP addresses in ipv4hint/ipv6hint.
+ final List<InetAddress> out = getAddressesFromAdditionalSection();
+ if (out.size() > 0) return out;
+
+ return record.getAddresses();
+ }
+
+ /**
+ * Returns the value of SVCB key dohpath that associated with the given protocol alpn.
+ * If the alpn is not supported, a null is returned.
+ */
+ @Nullable
+ public String getDohPath(@NonNull String alpn) {
+ final DnsSvcbRecord record = findSvcbRecord(alpn);
+ return (record != null) ? record.getDohPath() : null;
+ }
+
+ /**
+ * Returns the DnsSvcbRecord associated with the given protocol alpn.
+ * If the alpn is not supported, a null is returned.
+ */
+ @Nullable
+ private DnsSvcbRecord findSvcbRecord(@NonNull String alpn) {
+ for (final DnsRecord record : mRecords[ANSECTION]) {
+ if (record instanceof DnsSvcbRecord) {
+ final DnsSvcbRecord svcbRecord = (DnsSvcbRecord) record;
+ if (svcbRecord.getAlpns().contains(alpn)) {
+ return svcbRecord;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the IP addresses in additional section.
+ */
+ @NonNull
+ private List<InetAddress> getAddressesFromAdditionalSection() {
+ final List<InetAddress> out = new ArrayList<InetAddress>();
+ if (mHeader.getRecordCount(ARSECTION) == 0) {
+ return out;
+ }
+ for (final DnsRecord record : mRecords[ARSECTION]) {
+ if (record.nsType != TYPE_A && record.nsType != TYPE_AAAA) {
+ Log.d(TAG, "Found type other than A/AAAA in Additional section: " + record.nsType);
+ continue;
+ }
+ try {
+ out.add(InetAddress.getByAddress(record.getRR()));
+ } catch (UnknownHostException e) {
+ Log.w(TAG, "Failed to parse address");
+ }
+ }
+ return out;
+ }
+
+ @Override
+ public String toString() {
+ final StringJoiner out = new StringJoiner(" ");
+ out.add("QUERY: [" + TextUtils.join(", ", mRecords[QDSECTION]) + "]");
+ out.add("ANSWER: [" + TextUtils.join(", ", mRecords[ANSECTION]) + "]");
+ out.add("AUTHORITY: [" + TextUtils.join(", ", mRecords[NSSECTION]) + "]");
+ out.add("ADDITIONAL: [" + TextUtils.join(", ", mRecords[ARSECTION]) + "]");
+ return out.toString();
+ }
+
+ /**
+ * Creates a DnsSvcbPacket object from the given wire-format DNS answer.
+ */
+ public static DnsSvcbPacket fromResponse(@NonNull byte[] data) throws DnsPacket.ParseException {
+ DnsSvcbPacket out = new DnsSvcbPacket(data);
+ if (!out.isResponse()) {
+ throw new DnsPacket.ParseException("Not an answer packet");
+ }
+ return out;
+ }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/DnsSvcbRecord.java b/staticlibs/framework/com/android/net/module/util/DnsSvcbRecord.java
new file mode 100644
index 0000000..669725c
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/DnsSvcbRecord.java
@@ -0,0 +1,516 @@
+/*
+ * 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.net.module.util;
+
+import static android.net.DnsResolver.CLASS_IN;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.net.module.util.DnsPacket.ParseException;
+
+import android.annotation.NonNull;
+import android.text.TextUtils;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ShortBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.StringJoiner;
+
+/**
+ * A class for an SVCB record.
+ * https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml
+ * @hide
+ */
+@VisibleForTesting(visibility = PACKAGE)
+public final class DnsSvcbRecord extends DnsPacket.DnsRecord {
+ /**
+ * The following SvcParamKeys KEY_* are defined in
+ * https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml.
+ */
+
+ // The SvcParamKey "mandatory". The associated implementation of SvcParam is SvcParamMandatory.
+ private static final int KEY_MANDATORY = 0;
+
+ // The SvcParamKey "alpn". The associated implementation of SvcParam is SvcParamAlpn.
+ private static final int KEY_ALPN = 1;
+
+ // The SvcParamKey "no-default-alpn". The associated implementation of SvcParam is
+ // SvcParamNoDefaultAlpn.
+ private static final int KEY_NO_DEFAULT_ALPN = 2;
+
+ // The SvcParamKey "port". The associated implementation of SvcParam is SvcParamPort.
+ private static final int KEY_PORT = 3;
+
+ // The SvcParamKey "ipv4hint". The associated implementation of SvcParam is SvcParamIpv4Hint.
+ private static final int KEY_IPV4HINT = 4;
+
+ // The SvcParamKey "ech". The associated implementation of SvcParam is SvcParamEch.
+ private static final int KEY_ECH = 5;
+
+ // The SvcParamKey "ipv6hint". The associated implementation of SvcParam is SvcParamIpv6Hint.
+ private static final int KEY_IPV6HINT = 6;
+
+ // The SvcParamKey "dohpath". The associated implementation of SvcParam is SvcParamDohPath.
+ private static final int KEY_DOHPATH = 7;
+
+ // The minimal size of a SvcParam.
+ // https://www.ietf.org/archive/id/draft-ietf-dnsop-svcb-https-12.html#name-rdata-wire-format
+ private static final int MINSVCPARAMSIZE = 4;
+
+ private static final String TAG = DnsSvcbRecord.class.getSimpleName();
+
+ private final int mSvcPriority;
+
+ @NonNull
+ private final String mTargetName;
+
+ @NonNull
+ private final SparseArray<SvcParam> mAllSvcParams = new SparseArray<>();
+
+ @VisibleForTesting(visibility = PACKAGE)
+ public DnsSvcbRecord(@DnsPacket.RecordType int rType, @NonNull ByteBuffer buff)
+ throws IllegalStateException, ParseException {
+ super(rType, buff);
+ if (nsType != DnsPacket.TYPE_SVCB) {
+ throw new IllegalStateException("incorrect nsType: " + nsType);
+ }
+ if (nsClass != CLASS_IN) {
+ throw new ParseException("incorrect nsClass: " + nsClass);
+ }
+
+ // DNS Record in Question Section doesn't have Rdata.
+ if (rType == DnsPacket.QDSECTION) {
+ mSvcPriority = 0;
+ mTargetName = "";
+ return;
+ }
+
+ final byte[] rdata = getRR();
+ if (rdata == null) {
+ throw new ParseException("SVCB rdata is empty");
+ }
+
+ final ByteBuffer buf = ByteBuffer.wrap(rdata).asReadOnlyBuffer();
+ mSvcPriority = Short.toUnsignedInt(buf.getShort());
+ mTargetName = DnsPacketUtils.DnsRecordParser.parseName(buf, 0 /* Parse depth */,
+ false /* isNameCompressionSupported */);
+
+ if (mTargetName.length() > DnsPacket.DnsRecord.MAXNAMESIZE) {
+ throw new ParseException(
+ "Failed to parse SVCB target name, name size is too long: "
+ + mTargetName.length());
+ }
+ while (buf.remaining() >= MINSVCPARAMSIZE) {
+ final SvcParam svcParam = parseSvcParam(buf);
+ final int key = svcParam.getKey();
+ if (mAllSvcParams.get(key) != null) {
+ throw new ParseException("Invalid DnsSvcbRecord, key " + key + " is repeated");
+ }
+ mAllSvcParams.put(key, svcParam);
+ }
+ if (buf.hasRemaining()) {
+ throw new ParseException("Invalid DnsSvcbRecord. Got "
+ + buf.remaining() + " remaining bytes after parsing");
+ }
+ }
+
+ /**
+ * Returns the TargetName.
+ */
+ @VisibleForTesting(visibility = PACKAGE)
+ @NonNull
+ public String getTargetName() {
+ return mTargetName;
+ }
+
+ /**
+ * Returns an unmodifiable list of alpns from SvcParam alpn.
+ */
+ @VisibleForTesting(visibility = PACKAGE)
+ @NonNull
+ public List<String> getAlpns() {
+ final SvcParamAlpn sp = (SvcParamAlpn) mAllSvcParams.get(KEY_ALPN);
+ final List<String> list = (sp != null) ? sp.getValue() : Collections.EMPTY_LIST;
+ return Collections.unmodifiableList(list);
+ }
+
+ /**
+ * Returns the port number from SvcParam port.
+ */
+ @VisibleForTesting(visibility = PACKAGE)
+ public int getPort() {
+ final SvcParamPort sp = (SvcParamPort) mAllSvcParams.get(KEY_PORT);
+ return (sp != null) ? sp.getValue() : -1;
+ }
+
+ /**
+ * Returns a list of the IP addresses from both of SvcParam ipv4hint and ipv6hint.
+ */
+ @VisibleForTesting(visibility = PACKAGE)
+ @NonNull
+ public List<InetAddress> getAddresses() {
+ final List<InetAddress> out = new ArrayList<>();
+ final SvcParamIpHint sp4 = (SvcParamIpHint) mAllSvcParams.get(KEY_IPV4HINT);
+ if (sp4 != null) {
+ out.addAll(sp4.getValue());
+ }
+ final SvcParamIpHint sp6 = (SvcParamIpHint) mAllSvcParams.get(KEY_IPV6HINT);
+ if (sp6 != null) {
+ out.addAll(sp6.getValue());
+ }
+ return out;
+ }
+
+ /**
+ * Returns the doh path from SvcParam dohPath.
+ */
+ @VisibleForTesting(visibility = PACKAGE)
+ @NonNull
+ public String getDohPath() {
+ final SvcParamDohPath sp = (SvcParamDohPath) mAllSvcParams.get(KEY_DOHPATH);
+ return (sp != null) ? sp.getValue() : "";
+ }
+
+ @Override
+ public String toString() {
+ if (rType == DnsPacket.QDSECTION) {
+ return dName + " IN SVCB";
+ }
+
+ final StringJoiner sj = new StringJoiner(" ");
+ for (int i = 0; i < mAllSvcParams.size(); i++) {
+ sj.add(mAllSvcParams.valueAt(i).toString());
+ }
+ return dName + " " + ttl + " IN SVCB " + mSvcPriority + " " + mTargetName + " "
+ + sj.toString();
+ }
+
+ private static SvcParam parseSvcParam(@NonNull ByteBuffer buf) throws ParseException {
+ try {
+ final int key = Short.toUnsignedInt(buf.getShort());
+ switch (key) {
+ case KEY_MANDATORY: return new SvcParamMandatory(buf);
+ case KEY_ALPN: return new SvcParamAlpn(buf);
+ case KEY_NO_DEFAULT_ALPN: return new SvcParamNoDefaultAlpn(buf);
+ case KEY_PORT: return new SvcParamPort(buf);
+ case KEY_IPV4HINT: return new SvcParamIpv4Hint(buf);
+ case KEY_ECH: return new SvcParamEch(buf);
+ case KEY_IPV6HINT: return new SvcParamIpv6Hint(buf);
+ case KEY_DOHPATH: return new SvcParamDohPath(buf);
+ default: return new SvcParamGeneric(key, buf);
+ }
+ } catch (BufferUnderflowException e) {
+ throw new ParseException("Malformed packet", e);
+ }
+ }
+
+ /**
+ * The base class for all SvcParam.
+ */
+ private abstract static class SvcParam {
+ private final int mKey;
+
+ SvcParam(int key) {
+ mKey = key;
+ }
+
+ int getKey() {
+ return mKey;
+ }
+ }
+
+ private static class SvcParamMandatory extends SvcParam {
+ private final short[] mValue;
+
+ private SvcParamMandatory(@NonNull ByteBuffer buf) throws BufferUnderflowException,
+ ParseException {
+ super(KEY_MANDATORY);
+ // The caller already read 2 bytes for SvcParamKey.
+ final int len = Short.toUnsignedInt(buf.getShort());
+ final ByteBuffer svcParamValue = sliceAndAdvance(buf, len);
+ mValue = SvcParamValueUtil.toShortArray(svcParamValue);
+ if (mValue.length == 0) {
+ throw new ParseException("mandatory value must be non-empty");
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringJoiner valueJoiner = new StringJoiner(",");
+ for (short key : mValue) {
+ valueJoiner.add(toKeyName(key));
+ }
+ return toKeyName(getKey()) + "=" + valueJoiner.toString();
+ }
+ }
+
+ private static class SvcParamAlpn extends SvcParam {
+ private final List<String> mValue;
+
+ SvcParamAlpn(@NonNull ByteBuffer buf) throws BufferUnderflowException, ParseException {
+ super(KEY_ALPN);
+ // The caller already read 2 bytes for SvcParamKey.
+ final int len = Short.toUnsignedInt(buf.getShort());
+ final ByteBuffer svcParamValue = sliceAndAdvance(buf, len);
+ mValue = SvcParamValueUtil.toStringList(svcParamValue);
+ if (mValue.isEmpty()) {
+ throw new ParseException("alpn value must be non-empty");
+ }
+ }
+
+ List<String> getValue() {
+ return Collections.unmodifiableList(mValue);
+ }
+
+ @Override
+ public String toString() {
+ return toKeyName(getKey()) + "=" + TextUtils.join(",", mValue);
+ }
+ }
+
+ private static class SvcParamNoDefaultAlpn extends SvcParam {
+ SvcParamNoDefaultAlpn(@NonNull ByteBuffer buf) throws BufferUnderflowException,
+ ParseException {
+ super(KEY_NO_DEFAULT_ALPN);
+ // The caller already read 2 bytes for SvcParamKey.
+ final int len = buf.getShort();
+ if (len != 0) {
+ throw new ParseException("no-default-alpn value must be empty");
+ }
+ }
+
+ @Override
+ public String toString() {
+ return toKeyName(getKey());
+ }
+ }
+
+ private static class SvcParamPort extends SvcParam {
+ private final int mValue;
+
+ SvcParamPort(@NonNull ByteBuffer buf) throws BufferUnderflowException, ParseException {
+ super(KEY_PORT);
+ // The caller already read 2 bytes for SvcParamKey.
+ final int len = buf.getShort();
+ if (len != Short.BYTES) {
+ throw new ParseException("key port len is not 2 but " + len);
+ }
+ mValue = Short.toUnsignedInt(buf.getShort());
+ }
+
+ int getValue() {
+ return mValue;
+ }
+
+ @Override
+ public String toString() {
+ return toKeyName(getKey()) + "=" + mValue;
+ }
+ }
+
+ private static class SvcParamIpHint extends SvcParam {
+ private final List<InetAddress> mValue;
+
+ private SvcParamIpHint(int key, @NonNull ByteBuffer buf, int addrLen) throws
+ BufferUnderflowException, ParseException {
+ super(key);
+ // The caller already read 2 bytes for SvcParamKey.
+ final int len = Short.toUnsignedInt(buf.getShort());
+ final ByteBuffer svcParamValue = sliceAndAdvance(buf, len);
+ mValue = SvcParamValueUtil.toInetAddressList(svcParamValue, addrLen);
+ if (mValue.isEmpty()) {
+ throw new ParseException(toKeyName(getKey()) + " value must be non-empty");
+ }
+ }
+
+ List<InetAddress> getValue() {
+ return Collections.unmodifiableList(mValue);
+ }
+
+ @Override
+ public String toString() {
+ final StringJoiner valueJoiner = new StringJoiner(",");
+ for (InetAddress ip : mValue) {
+ valueJoiner.add(ip.getHostAddress());
+ }
+ return toKeyName(getKey()) + "=" + valueJoiner.toString();
+ }
+ }
+
+ private static class SvcParamIpv4Hint extends SvcParamIpHint {
+ SvcParamIpv4Hint(@NonNull ByteBuffer buf) throws BufferUnderflowException, ParseException {
+ super(KEY_IPV4HINT, buf, NetworkStackConstants.IPV4_ADDR_LEN);
+ }
+ }
+
+ private static class SvcParamIpv6Hint extends SvcParamIpHint {
+ SvcParamIpv6Hint(@NonNull ByteBuffer buf) throws BufferUnderflowException, ParseException {
+ super(KEY_IPV6HINT, buf, NetworkStackConstants.IPV6_ADDR_LEN);
+ }
+ }
+
+ private static class SvcParamEch extends SvcParamGeneric {
+ SvcParamEch(@NonNull ByteBuffer buf) throws BufferUnderflowException, ParseException {
+ super(KEY_ECH, buf);
+ }
+ }
+
+ private static class SvcParamDohPath extends SvcParam {
+ private final String mValue;
+
+ SvcParamDohPath(@NonNull ByteBuffer buf) throws BufferUnderflowException, ParseException {
+ super(KEY_DOHPATH);
+ // The caller already read 2 bytes for SvcParamKey.
+ final int len = Short.toUnsignedInt(buf.getShort());
+ final byte[] value = new byte[len];
+ buf.get(value);
+ mValue = new String(value, StandardCharsets.UTF_8);
+ }
+
+ String getValue() {
+ return mValue;
+ }
+
+ @Override
+ public String toString() {
+ return toKeyName(getKey()) + "=" + mValue;
+ }
+ }
+
+ // For other unrecognized and unimplemented SvcParams, they are stored as SvcParamGeneric.
+ private static class SvcParamGeneric extends SvcParam {
+ private final byte[] mValue;
+
+ SvcParamGeneric(int key, @NonNull ByteBuffer buf) throws BufferUnderflowException,
+ ParseException {
+ super(key);
+ // The caller already read 2 bytes for SvcParamKey.
+ final int len = Short.toUnsignedInt(buf.getShort());
+ mValue = new byte[len];
+ buf.get(mValue);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder out = new StringBuilder();
+ out.append(toKeyName(getKey()));
+ if (mValue != null && mValue.length > 0) {
+ out.append("=");
+ out.append(HexDump.toHexString(mValue));
+ }
+ return out.toString();
+ }
+ }
+
+ private static String toKeyName(int key) {
+ switch (key) {
+ case KEY_MANDATORY: return "mandatory";
+ case KEY_ALPN: return "alpn";
+ case KEY_NO_DEFAULT_ALPN: return "no-default-alpn";
+ case KEY_PORT: return "port";
+ case KEY_IPV4HINT: return "ipv4hint";
+ case KEY_ECH: return "ech";
+ case KEY_IPV6HINT: return "ipv6hint";
+ case KEY_DOHPATH: return "dohpath";
+ default: return "key" + key;
+ }
+ }
+
+ /**
+ * Returns a read-only ByteBuffer (with position = 0, limit = `length`, and capacity = `length`)
+ * sliced from `buf`'s current position, and moves the position of `buf` by `length`.
+ */
+ @VisibleForTesting(visibility = PRIVATE)
+ public static ByteBuffer sliceAndAdvance(@NonNull ByteBuffer buf, int length)
+ throws BufferUnderflowException {
+ if (buf.remaining() < length) {
+ throw new BufferUnderflowException();
+ }
+ final int pos = buf.position();
+
+ // `out` equals to `buf.slice(pos, length)` that is supported in API level 34.
+ final ByteBuffer out = ((ByteBuffer) buf.slice().limit(length)).slice();
+
+ buf.position(pos + length);
+ return out.asReadOnlyBuffer();
+ }
+
+ // A utility to convert the byte array of SvcParamValue to other types.
+ private static class SvcParamValueUtil {
+ // Refer to draft-ietf-dnsop-svcb-https-10#section-7.1 for the wire format of alpn.
+ @NonNull
+ private static List<String> toStringList(@NonNull ByteBuffer buf)
+ throws BufferUnderflowException, ParseException {
+ final List<String> out = new ArrayList<>();
+ while (buf.hasRemaining()) {
+ final int alpnLen = Byte.toUnsignedInt(buf.get());
+ if (alpnLen == 0) {
+ throw new ParseException("alpn should not be an empty string");
+ }
+ final byte[] alpn = new byte[alpnLen];
+ buf.get(alpn);
+ out.add(new String(alpn, StandardCharsets.UTF_8));
+ }
+ return out;
+ }
+
+ // Refer to draft-ietf-dnsop-svcb-https-10#section-7.5 for the wire format of SvcParamKey
+ // "mandatory".
+ @NonNull
+ private static short[] toShortArray(@NonNull ByteBuffer buf)
+ throws BufferUnderflowException, ParseException {
+ if (buf.remaining() % Short.BYTES != 0) {
+ throw new ParseException("Can't parse whole byte array");
+ }
+ final ShortBuffer sb = buf.asShortBuffer();
+ final short[] out = new short[sb.remaining()];
+ sb.get(out);
+ return out;
+ }
+
+ // Refer to draft-ietf-dnsop-svcb-https-10#section-7.4 for the wire format of ipv4hint and
+ // ipv6hint.
+ @NonNull
+ private static List<InetAddress> toInetAddressList(@NonNull ByteBuffer buf, int addrLen)
+ throws BufferUnderflowException, ParseException {
+ if (buf.remaining() % addrLen != 0) {
+ throw new ParseException("Can't parse whole byte array");
+ }
+
+ final List<InetAddress> out = new ArrayList<>();
+ final byte[] addr = new byte[addrLen];
+ while (buf.remaining() >= addrLen) {
+ buf.get(addr);
+ try {
+ out.add(InetAddress.getByAddress(addr));
+ } catch (UnknownHostException e) {
+ throw new ParseException("Can't parse byte array as an IP address");
+ }
+ }
+ return out;
+ }
+ }
+}
diff --git a/staticlibs/native/bpf_headers/BpfRingbufTest.cpp b/staticlibs/native/bpf_headers/BpfRingbufTest.cpp
index 6c0841c..e4de812 100644
--- a/staticlibs/native/bpf_headers/BpfRingbufTest.cpp
+++ b/staticlibs/native/bpf_headers/BpfRingbufTest.cpp
@@ -72,12 +72,15 @@
auto result = BpfRingbuf<uint64_t>::Create(mRingbufPath.c_str());
ASSERT_RESULT_OK(result);
+ EXPECT_TRUE(result.value()->isEmpty());
for (int i = 0; i < n; i++) {
RunProgram();
}
+ EXPECT_FALSE(result.value()->isEmpty());
EXPECT_THAT(result.value()->ConsumeAll(callback), HasValue(n));
+ EXPECT_TRUE(result.value()->isEmpty());
EXPECT_EQ(output, TEST_RINGBUF_MAGIC_NUM);
EXPECT_EQ(run_count, n);
}
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfMap.h b/staticlibs/native/bpf_headers/include/bpf/BpfMap.h
index 5d7eb0d..3fede3c 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfMap.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfMap.h
@@ -78,7 +78,7 @@
Result<Key> getFirstKey() const {
Key firstKey;
if (getFirstMapKey(mMapFd, &firstKey)) {
- return ErrnoErrorf("Get firstKey map {} failed", mMapFd.get());
+ return ErrnoErrorf("BpfMap::getFirstKey() failed");
}
return firstKey;
}
@@ -86,7 +86,7 @@
Result<Key> getNextKey(const Key& key) const {
Key nextKey;
if (getNextMapKey(mMapFd, &key, &nextKey)) {
- return ErrnoErrorf("Get next key of map {} failed", mMapFd.get());
+ return ErrnoErrorf("BpfMap::getNextKey() failed");
}
return nextKey;
}
@@ -94,7 +94,7 @@
Result<Value> readValue(const Key key) const {
Value value;
if (findMapEntry(mMapFd, &key, &value)) {
- return ErrnoErrorf("Read value of map {} failed", mMapFd.get());
+ return ErrnoErrorf("BpfMap::readValue() failed");
}
return value;
}
@@ -243,14 +243,14 @@
Result<void> writeValue(const Key& key, const Value& value, uint64_t flags) {
if (writeToMapEntry(mMapFd, &key, &value, flags)) {
- return ErrnoErrorf("Write to map {} failed", mMapFd.get());
+ return ErrnoErrorf("BpfMap::writeValue() failed");
}
return {};
}
Result<void> deleteValue(const Key& key) {
if (deleteMapEntry(mMapFd, &key)) {
- return ErrnoErrorf("Delete entry from map {} failed", mMapFd.get());
+ return ErrnoErrorf("BpfMap::deleteValue() failed");
}
return {};
}
@@ -280,7 +280,7 @@
if (map_flags & BPF_F_RDONLY) abort();
mMapFd.reset(createMap(map_type, sizeof(Key), sizeof(Value), max_entries,
map_flags));
- if (!mMapFd.ok()) return ErrnoErrorf("Unable to create map.");
+ if (!mMapFd.ok()) return ErrnoErrorf("BpfMap::resetMap() failed");
abortOnMismatch(/* writable */ true);
return {};
}
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
index dd1504c..9aff790 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
@@ -39,6 +39,8 @@
mProducerPos = nullptr;
}
+ bool isEmpty(void);
+
protected:
// Non-initializing constructor, used by Create.
BpfRingbufBase(size_t value_size) : mValueSize(value_size) {}
@@ -197,6 +199,13 @@
return {};
}
+inline bool BpfRingbufBase::isEmpty(void) {
+ uint32_t prod_pos = mProducerPos->load(std::memory_order_acquire);
+ // Only userspace writes to mConsumerPos, so no need to use std::memory_order_acquire
+ uint64_t cons_pos = mConsumerPos->load(std::memory_order_relaxed);
+ return (cons_pos & 0xFFFFFFFF) == prod_pos;
+}
+
inline base::Result<int> BpfRingbufBase::ConsumeAll(
const std::function<void(const void*)>& callback) {
int64_t count = 0;
diff --git a/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
index f93d6e1..b92f107 100644
--- a/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
+++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
@@ -15,6 +15,8 @@
*/
#include <errno.h>
+#include <linux/pfkeyv2.h>
+#include <sys/socket.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedLocalRef.h>
@@ -117,6 +119,22 @@
return throwIfNotEnoent(env, "nativeFindMapEntry", ret, errno);
}
+static void com_android_net_module_util_BpfMap_nativeSynchronizeKernelRCU(JNIEnv *env,
+ jclass clazz) {
+ const int pfSocket = socket(AF_KEY, SOCK_RAW | SOCK_CLOEXEC, PF_KEY_V2);
+
+ if (pfSocket < 0) {
+ jniThrowErrnoException(env, "nativeSynchronizeKernelRCU:socket", errno);
+ return;
+ }
+
+ if (close(pfSocket)) {
+ jniThrowErrnoException(env, "nativeSynchronizeKernelRCU:close", errno);
+ return;
+ }
+ return;
+}
+
/*
* JNI registration.
*/
@@ -132,6 +150,8 @@
(void*) com_android_net_module_util_BpfMap_nativeGetNextMapKey },
{ "nativeFindMapEntry", "(I[B[B)Z",
(void*) com_android_net_module_util_BpfMap_nativeFindMapEntry },
+ { "nativeSynchronizeKernelRCU", "()V",
+ (void*) com_android_net_module_util_BpfMap_nativeSynchronizeKernelRCU },
};
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java
index 28e183a..88d9e1e 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java
@@ -203,6 +203,30 @@
"test.com", CLASS_IN, 0 /* ttl */, "example.com"));
}
+ /** Verifies that the type of implementation returned from DnsRecord#parse is correct */
+ @Test
+ public void testDnsRecordParse() throws IOException {
+ final byte[] svcbQuestionRecord = new byte[] {
+ 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00, /* Name */
+ 0x00, 0x40, /* Type */
+ 0x00, 0x01, /* Class */
+ };
+ assertTrue(DnsPacket.DnsRecord.parse(DnsPacket.QDSECTION,
+ ByteBuffer.wrap(svcbQuestionRecord)) instanceof DnsSvcbRecord);
+
+ final byte[] svcbAnswerRecord = new byte[] {
+ 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', 0x00, /* Name */
+ 0x00, 0x40, /* Type */
+ 0x00, 0x01, /* Class */
+ 0x00, 0x00, 0x01, 0x2b, /* TTL */
+ 0x00, 0x0b, /* Data length */
+ 0x00, 0x01, /* SvcPriority */
+ 0x03, 'd', 'o', 't', 0x03, 'c', 'o', 'm', 0x00, /* TargetName */
+ };
+ assertTrue(DnsPacket.DnsRecord.parse(DnsPacket.ANSECTION,
+ ByteBuffer.wrap(svcbAnswerRecord)) instanceof DnsSvcbRecord);
+ }
+
/**
* Verifies ttl/rData error handling when parsing
* {@link DnsPacket.DnsRecord} from bytes.
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DnsSvcbPacketTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DnsSvcbPacketTest.java
new file mode 100644
index 0000000..6778f8a
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DnsSvcbPacketTest.java
@@ -0,0 +1,606 @@
+/*
+ * 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.net.module.util;
+
+import static android.net.DnsResolver.CLASS_IN;
+import static android.net.DnsResolver.TYPE_A;
+import static android.net.DnsResolver.TYPE_AAAA;
+
+import static com.android.net.module.util.DnsPacket.TYPE_SVCB;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.annotation.NonNull;
+import android.net.InetAddresses;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class DnsSvcbPacketTest {
+ private static final short TEST_TRANSACTION_ID = 0x4321;
+ private static final byte[] TEST_DNS_RESPONSE_HEADER_FLAG = new byte[] { (byte) 0x81, 0x00 };
+
+ // A common DNS SVCB Question section with Name = "_dns.resolver.arpa".
+ private static final byte[] TEST_DNS_SVCB_QUESTION_SECTION = new byte[] {
+ 0x04, '_', 'd', 'n', 's', 0x08, 'r', 'e', 's', 'o', 'l', 'v', 'e', 'r',
+ 0x04, 'a', 'r', 'p', 'a', 0x00, 0x00, 0x40, 0x00, 0x01,
+ };
+
+ // mandatory=ipv4hint,alpn,key333
+ private static final byte[] TEST_SVC_PARAM_MANDATORY = new byte[] {
+ 0x00, 0x00, 0x00, 0x06, 0x00, 0x04, 0x00, 0x01, 0x01, 0x4d,
+ };
+
+ // alpn=doq
+ private static final byte[] TEST_SVC_PARAM_ALPN_DOQ = new byte[] {
+ 0x00, 0x01, 0x00, 0x04, 0x03, 'd', 'o', 'q'
+ };
+
+ // alpn=h2,http/1.1
+ private static final byte[] TEST_SVC_PARAM_ALPN_HTTPS = new byte[] {
+ 0x00, 0x01, 0x00, 0x0c, 0x02, 'h', '2',
+ 0x08, 'h', 't', 't', 'p', '/', '1', '.', '1',
+ };
+
+ // no-default-alpn
+ private static final byte[] TEST_SVC_PARAM_NO_DEFAULT_ALPN = new byte[] {
+ 0x00, 0x02, 0x00, 0x00,
+ };
+
+ // port=5353
+ private static final byte[] TEST_SVC_PARAM_PORT = new byte[] {
+ 0x00, 0x03, 0x00, 0x02, 0x14, (byte) 0xe9,
+ };
+
+ // ipv4hint=1.2.3.4,6.7.8.9
+ private static final byte[] TEST_SVC_PARAM_IPV4HINT_1 = new byte[] {
+ 0x00, 0x04, 0x00, 0x08, 0x01, 0x02, 0x03, 0x04, 0x06, 0x07, 0x08, 0x09,
+ };
+
+ // ipv4hint=4.3.2.1
+ private static final byte[] TEST_SVC_PARAM_IPV4HINT_2 = new byte[] {
+ 0x00, 0x04, 0x00, 0x04, 0x04, 0x03, 0x02, 0x01,
+ };
+
+ // ech=aBcDe
+ private static final byte[] TEST_SVC_PARAM_ECH = new byte[] {
+ 0x00, 0x05, 0x00, 0x05, 'a', 'B', 'c', 'D', 'e',
+ };
+
+ // ipv6hint=2001:db8::1
+ private static final byte[] TEST_SVC_PARAM_IPV6HINT = new byte[] {
+ 0x00, 0x06, 0x00, 0x10, 0x20, 0x01, 0x0d, (byte) 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ };
+
+ // dohpath=/some-path{?dns}
+ private static final byte[] TEST_SVC_PARAM_DOHPATH = new byte[] {
+ 0x00, 0x07, 0x00, 0x10,
+ '/', 's', 'o', 'm', 'e', '-', 'p', 'a', 't', 'h', '{', '?', 'd', 'n', 's', '}',
+ };
+
+ // key12345=1A2B0C
+ private static final byte[] TEST_SVC_PARAM_GENERIC_WITH_VALUE = new byte[] {
+ 0x30, 0x39, 0x00, 0x03, 0x1a, 0x2b, 0x0c,
+ };
+
+ // key12346
+ private static final byte[] TEST_SVC_PARAM_GENERIC_WITHOUT_VALUE = new byte[] {
+ 0x30, 0x3a, 0x00, 0x00,
+ };
+
+ private static byte[] makeDnsResponseHeaderAsByteArray(int qdcount, int ancount, int nscount,
+ int arcount) {
+ final ByteBuffer buffer = ByteBuffer.wrap(new byte[12]);
+ buffer.putShort(TEST_TRANSACTION_ID); /* Transaction ID */
+ buffer.put(TEST_DNS_RESPONSE_HEADER_FLAG); /* Flags */
+ buffer.putShort((short) qdcount);
+ buffer.putShort((short) ancount);
+ buffer.putShort((short) nscount);
+ buffer.putShort((short) arcount);
+ return buffer.array();
+ }
+
+ private static DnsSvcbRecord makeDnsSvcbRecordFromByteArray(@NonNull byte[] data)
+ throws IOException {
+ return new DnsSvcbRecord(DnsPacket.ANSECTION, ByteBuffer.wrap(data));
+ }
+
+ private static DnsSvcbRecord makeDnsSvcbRecordWithSingleSvcParam(@NonNull byte[] svcParam)
+ throws IOException {
+ return makeDnsSvcbRecordFromByteArray(new TestDnsRecordByteArrayBuilder()
+ .setRRType(TYPE_SVCB)
+ .setTargetName("test.com")
+ .addRdata(svcParam)
+ .build());
+ }
+
+ // Converts a Short to a byte array in big endian.
+ private static byte[] shortToByteArray(short value) {
+ return new byte[] { (byte) (value >> 8), (byte) value };
+ }
+
+ private static byte[] getRemainingByteArray(@NonNull ByteBuffer buffer) {
+ final byte[] out = new byte[buffer.remaining()];
+ buffer.get(out);
+ return out;
+ }
+
+ // A utility to make a DNS record as byte array.
+ private static class TestDnsRecordByteArrayBuilder {
+ private static final byte[] NAME_COMPRESSION_POINTER = new byte[] { (byte) 0xc0, 0x0c };
+
+ private final String mRRName = "dns.com";
+ private short mRRType = 0;
+ private final short mRRClass = CLASS_IN;
+ private final int mRRTtl = 10;
+ private int mRdataLen = 0;
+ private final ArrayList<byte[]> mRdata = new ArrayList<>();
+ private String mTargetName = null;
+ private short mSvcPriority = 1;
+ private boolean mNameCompression = false;
+
+ TestDnsRecordByteArrayBuilder setNameCompression(boolean value) {
+ mNameCompression = value;
+ return this;
+ }
+
+ TestDnsRecordByteArrayBuilder setRRType(int value) {
+ mRRType = (short) value;
+ return this;
+ }
+
+ TestDnsRecordByteArrayBuilder setTargetName(@NonNull String value) throws IOException {
+ mTargetName = value;
+ return this;
+ }
+
+ TestDnsRecordByteArrayBuilder setSvcPriority(int value) {
+ mSvcPriority = (short) value;
+ return this;
+ }
+
+ TestDnsRecordByteArrayBuilder addRdata(@NonNull byte[] value) {
+ mRdata.add(value);
+ mRdataLen += value.length;
+ return this;
+ }
+
+ byte[] build() throws IOException {
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ final byte[] name = mNameCompression ? NAME_COMPRESSION_POINTER
+ : DnsPacketUtils.DnsRecordParser.domainNameToLabels(mRRName);
+ os.write(name);
+ os.write(shortToByteArray(mRRType));
+ os.write(shortToByteArray(mRRClass));
+ os.write(HexDump.toByteArray(mRRTtl));
+ if (mTargetName == null) {
+ os.write(shortToByteArray((short) mRdataLen));
+ } else {
+ final byte[] targetNameLabels =
+ DnsPacketUtils.DnsRecordParser.domainNameToLabels(mTargetName);
+ mRdataLen += (Short.BYTES + targetNameLabels.length);
+ os.write(shortToByteArray((short) mRdataLen));
+ os.write(shortToByteArray(mSvcPriority));
+ os.write(targetNameLabels);
+ }
+ for (byte[] data : mRdata) {
+ os.write(data);
+ }
+ return os.toByteArray();
+ }
+ }
+
+ @Test
+ public void testSliceAndAdvance() throws Exception {
+ final ByteBuffer buffer = ByteBuffer.wrap(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9});
+ final ByteBuffer slice1 = DnsSvcbRecord.sliceAndAdvance(buffer, 3);
+ final ByteBuffer slice2 = DnsSvcbRecord.sliceAndAdvance(buffer, 4);
+ assertEquals(0, slice1.position());
+ assertEquals(3, slice1.capacity());
+ assertEquals(3, slice1.remaining());
+ assertTrue(slice1.isReadOnly());
+ assertArrayEquals(new byte[] {1, 2, 3}, getRemainingByteArray(slice1));
+ assertEquals(0, slice2.position());
+ assertEquals(4, slice2.capacity());
+ assertEquals(4, slice2.remaining());
+ assertTrue(slice2.isReadOnly());
+ assertArrayEquals(new byte[] {4, 5, 6, 7}, getRemainingByteArray(slice2));
+
+ // Nothing is read if out-of-bound access happens.
+ assertThrows(BufferUnderflowException.class,
+ () -> DnsSvcbRecord.sliceAndAdvance(buffer, 5));
+ assertEquals(7, buffer.position());
+ assertEquals(9, buffer.capacity());
+ assertEquals(2, buffer.remaining());
+ assertArrayEquals(new byte[] {8, 9}, getRemainingByteArray(buffer));
+ }
+
+ @Test
+ public void testDnsSvcbRecord_svcParamMandatory() throws Exception {
+ final DnsSvcbRecord record = makeDnsSvcbRecordWithSingleSvcParam(TEST_SVC_PARAM_MANDATORY);
+ // Check the content returned from toString() for now because the getter function for
+ // this SvcParam hasn't been implemented.
+ // TODO(b/240259333): Consider adding DnsSvcbRecord.isMandatory(String alpn) when needed.
+ assertTrue(record.toString().contains("mandatory=ipv4hint,alpn,key333"));
+ }
+
+ @Test
+ public void testDnsSvcbRecord_svcParamAlpn() throws Exception {
+ final DnsSvcbRecord record = makeDnsSvcbRecordWithSingleSvcParam(TEST_SVC_PARAM_ALPN_HTTPS);
+ assertEquals(Arrays.asList("h2", "http/1.1"), record.getAlpns());
+ }
+
+ @Test
+ public void testDnsSvcbRecord_svcParamNoDefaultAlpn() throws Exception {
+ final DnsSvcbRecord record = makeDnsSvcbRecordWithSingleSvcParam(
+ TEST_SVC_PARAM_NO_DEFAULT_ALPN);
+ // Check the content returned from toString() for now because the getter function for
+ // this SvcParam hasn't been implemented.
+ // TODO(b/240259333): Consider adding DnsSvcbRecord.hasNoDefaultAlpn() when needed.
+ assertTrue(record.toString().contains("no-default-alpn"));
+ }
+
+ @Test
+ public void testDnsSvcbRecord_svcParamPort() throws Exception {
+ final DnsSvcbRecord record = makeDnsSvcbRecordWithSingleSvcParam(TEST_SVC_PARAM_PORT);
+ assertEquals(5353, record.getPort());
+ }
+
+ @Test
+ public void testDnsSvcbRecord_svcParamIpv4Hint() throws Exception {
+ final DnsSvcbRecord record = makeDnsSvcbRecordWithSingleSvcParam(TEST_SVC_PARAM_IPV4HINT_2);
+ assertEquals(Arrays.asList(InetAddresses.parseNumericAddress("4.3.2.1")),
+ record.getAddresses());
+ }
+
+ @Test
+ public void testDnsSvcbRecord_svcParamEch() throws Exception {
+ final DnsSvcbRecord record = makeDnsSvcbRecordWithSingleSvcParam(TEST_SVC_PARAM_ECH);
+ // Check the content returned from toString() for now because the getter function for
+ // this SvcParam hasn't been implemented.
+ // TODO(b/240259333): Consider adding DnsSvcbRecord.getEch() when needed.
+ assertTrue(record.toString().contains("ech=6142634465"));
+ }
+
+ @Test
+ public void testDnsSvcbRecord_svcParamIpv6Hint() throws Exception {
+ final DnsSvcbRecord record = makeDnsSvcbRecordWithSingleSvcParam(TEST_SVC_PARAM_IPV6HINT);
+ assertEquals(Arrays.asList(InetAddresses.parseNumericAddress("2001:db8::1")),
+ record.getAddresses());
+ }
+
+ @Test
+ public void testDnsSvcbRecord_svcParamDohPath() throws Exception {
+ final DnsSvcbRecord record = makeDnsSvcbRecordWithSingleSvcParam(TEST_SVC_PARAM_DOHPATH);
+ assertEquals("/some-path{?dns}", record.getDohPath());
+ }
+
+ @Test
+ public void testDnsSvcbRecord_svcParamGeneric_withValue() throws Exception {
+ final DnsSvcbRecord record = makeDnsSvcbRecordWithSingleSvcParam(
+ TEST_SVC_PARAM_GENERIC_WITH_VALUE);
+ // Check the content returned from toString() for now because the getter function for
+ // generic SvcParam hasn't been implemented.
+ // TODO(b/240259333): Consider adding DnsSvcbRecord.getValueFromGenericSvcParam(int key)
+ // when needed.
+ assertTrue(record.toString().contains("key12345=1A2B0C"));
+ }
+
+ @Test
+ public void testDnsSvcbRecord_svcParamGeneric_withoutValue() throws Exception {
+ final DnsSvcbRecord record = makeDnsSvcbRecordWithSingleSvcParam(
+ TEST_SVC_PARAM_GENERIC_WITHOUT_VALUE);
+ // Check the content returned from toString() for now because the getter function for
+ // generic SvcParam hasn't been implemented.
+ // TODO(b/240259333): Consider adding DnsSvcbRecord.getValueFromGenericSvcParam(int key)
+ // when needed.
+ assertTrue(record.toString().contains("key12346"));
+ }
+
+ @Test
+ public void testDnsSvcbRecord() throws Exception {
+ final DnsSvcbRecord record = makeDnsSvcbRecordFromByteArray(
+ new TestDnsRecordByteArrayBuilder()
+ .setRRType(TYPE_SVCB)
+ .setTargetName("doh.dns.com")
+ .addRdata(TEST_SVC_PARAM_ALPN_HTTPS)
+ .addRdata(TEST_SVC_PARAM_IPV4HINT_1)
+ .addRdata(TEST_SVC_PARAM_IPV6HINT)
+ .addRdata(TEST_SVC_PARAM_PORT)
+ .addRdata(TEST_SVC_PARAM_DOHPATH)
+ .build());
+ assertEquals("doh.dns.com", record.getTargetName());
+ assertEquals(Arrays.asList("h2", "http/1.1"), record.getAlpns());
+ assertEquals(5353, record.getPort());
+ assertEquals(Arrays.asList(
+ InetAddresses.parseNumericAddress("1.2.3.4"),
+ InetAddresses.parseNumericAddress("6.7.8.9"),
+ InetAddresses.parseNumericAddress("2001:db8::1")), record.getAddresses());
+ assertEquals("/some-path{?dns}", record.getDohPath());
+ }
+
+ @Test
+ public void testDnsSvcbRecord_createdFromNullObject() throws Exception {
+ assertThrows(NullPointerException.class, () -> makeDnsSvcbRecordFromByteArray(null));
+ }
+
+ @Test
+ public void testDnsSvcbRecord_invalidDnsRecord() throws Exception {
+ // The type is not SVCB.
+ final byte[] bytes1 = new TestDnsRecordByteArrayBuilder()
+ .setRRType(TYPE_A)
+ .addRdata(InetAddresses.parseNumericAddress("1.2.3.4").getAddress())
+ .build();
+ assertThrows(IllegalStateException.class, () -> makeDnsSvcbRecordFromByteArray(bytes1));
+
+ // TargetName is missing.
+ final byte[] bytes2 = new TestDnsRecordByteArrayBuilder()
+ .setRRType(TYPE_SVCB)
+ .addRdata(new byte[] { 0x01, 0x01 })
+ .build();
+ assertThrows(BufferUnderflowException.class, () -> makeDnsSvcbRecordFromByteArray(bytes2));
+
+ // Rdata is empty.
+ final byte[] bytes3 = new TestDnsRecordByteArrayBuilder()
+ .setRRType(TYPE_SVCB)
+ .build();
+ assertThrows(BufferUnderflowException.class, () -> makeDnsSvcbRecordFromByteArray(bytes3));
+ }
+
+ @Test
+ public void testDnsSvcbRecord_repeatedKeyIsInvalid() throws Exception {
+ final byte[] bytes = new TestDnsRecordByteArrayBuilder()
+ .setRRType(TYPE_SVCB)
+ .addRdata(TEST_SVC_PARAM_ALPN_HTTPS)
+ .addRdata(TEST_SVC_PARAM_ALPN_DOQ)
+ .build();
+ assertThrows(DnsPacket.ParseException.class, () -> makeDnsSvcbRecordFromByteArray(bytes));
+ }
+
+ @Test
+ public void testDnsSvcbRecord_invalidContent() throws Exception {
+ final List<byte[]> invalidContents = Arrays.asList(
+ // Invalid SvcParamValue for "mandatory":
+ // - SvcParamValue must not be empty.
+ // - SvcParamValue has less data than expected.
+ // - SvcParamValue has more data than expected.
+ // - SvcParamValue must be multiple of 2.
+ new byte[] { 0x00, 0x00, 0x00, 0x00},
+ new byte[] { 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x06 },
+ new byte[] { 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, },
+ new byte[] { 0x00, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00 },
+
+ // Invalid SvcParamValue for "alpn":
+ // - SvcParamValue must not be empty.
+ // - SvcParamValue has less data than expected.
+ // - SvcParamValue has more data than expected.
+ // - Alpn length is less than the actual data size.
+ // - Alpn length is more than the actual data size.
+ // - Alpn must be a non-empty string.
+ new byte[] { 0x00, 0x01, 0x00, 0x00},
+ new byte[] { 0x00, 0x01, 0x00, 0x02, 0x02, 'h', '2' },
+ new byte[] { 0x00, 0x01, 0x00, 0x05, 0x02, 'h', '2' },
+ new byte[] { 0x00, 0x01, 0x00, 0x04, 0x02, 'd', 'o', 't' },
+ new byte[] { 0x00, 0x01, 0x00, 0x04, 0x08, 'd', 'o', 't' },
+ new byte[] { 0x00, 0x01, 0x00, 0x08, 0x02, 'h', '2', 0x00 },
+
+ // Invalid SvcParamValue for "no-default-alpn":
+ // - SvcParamValue must be empty.
+ // - SvcParamValue length must be 0.
+ new byte[] { 0x00, 0x02, 0x00, 0x04, 'd', 'a', 't', 'a' },
+ new byte[] { 0x00, 0x02, 0x00, 0x04 },
+
+ // Invalid SvcParamValue for "port":
+ // - SvcParamValue must not be empty.
+ // - SvcParamValue has less data than expected.
+ // - SvcParamValue has more data than expected.
+ // - SvcParamValue length must be multiple of 2.
+ new byte[] { 0x00, 0x03, 0x00, 0x00 },
+ new byte[] { 0x00, 0x03, 0x00, 0x02, 0x01 },
+ new byte[] { 0x00, 0x03, 0x00, 0x02, 0x01, 0x02, 0x03 },
+ new byte[] { 0x00, 0x03, 0x00, 0x03, 0x01, 0x02, 0x03 },
+
+ // Invalid SvcParamValue for "ipv4hint":
+ // - SvcParamValue must not be empty.
+ // - SvcParamValue has less data than expected.
+ // - SvcParamValue has more data than expected.
+ // - SvcParamValue must be multiple of 4.
+ new byte[] { 0x00, 0x04, 0x00, 0x00 },
+ new byte[] { 0x00, 0x04, 0x00, 0x04, 0x08 },
+ new byte[] { 0x00, 0x04, 0x00, 0x04, 0x08, 0x08, 0x08, 0x08, 0x08 },
+ new byte[] { 0x00, 0x04, 0x00, 0x05, 0x08, 0x08, 0x08, 0x08 },
+
+ // Invalid SvcParamValue for "ipv6hint":
+ // - SvcParamValue must not be empty.
+ // - SvcParamValue has less data than expected.
+ // - SvcParamValue has more data than expected.
+ // - SvcParamValue must be multiple of 16.
+ new byte[] { 0x00, 0x06, 0x00, 0x00 },
+ new byte[] { 0x00, 0x06, 0x00, 0x10, 0x01 },
+ new byte[] { 0x00, 0x06, 0x00, 0x10, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17 },
+ new byte[] { 0x00, 0x06, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 }
+ );
+
+ for (byte[] content : invalidContents) {
+ final byte[] bytes = new TestDnsRecordByteArrayBuilder()
+ .setRRType(TYPE_SVCB)
+ .addRdata(content)
+ .build();
+ assertThrows(DnsPacket.ParseException.class,
+ () -> makeDnsSvcbRecordFromByteArray(bytes));
+ }
+ }
+
+ @Test
+ public void testDnsSvcbPacket_createdFromNullObject() throws Exception {
+ assertThrows(DnsPacket.ParseException.class, () -> DnsSvcbPacket.fromResponse(null));
+ }
+
+ @Test
+ public void testDnsSvcbPacket() throws Exception {
+ final String dohTargetName = "https.dns.com";
+ final String doqTargetName = "doq.dns.com";
+ final InetAddress[] expectedIpAddressesForHttps = new InetAddress[] {
+ InetAddresses.parseNumericAddress("1.2.3.4"),
+ InetAddresses.parseNumericAddress("6.7.8.9"),
+ InetAddresses.parseNumericAddress("2001:db8::1"),
+ };
+ final InetAddress[] expectedIpAddressesForDoq = new InetAddress[] {
+ InetAddresses.parseNumericAddress("4.3.2.1"),
+ };
+
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ os.write(makeDnsResponseHeaderAsByteArray(1 /* qdcount */, 2 /* ancount */, 0 /* nscount */,
+ 0 /* arcount */));
+ os.write(TEST_DNS_SVCB_QUESTION_SECTION);
+ // Add answer for alpn h2 and http/1.1.
+ os.write(new TestDnsRecordByteArrayBuilder()
+ .setNameCompression(true)
+ .setRRType(TYPE_SVCB)
+ .setTargetName(dohTargetName)
+ .addRdata(TEST_SVC_PARAM_ALPN_HTTPS)
+ .addRdata(TEST_SVC_PARAM_IPV4HINT_1)
+ .addRdata(TEST_SVC_PARAM_IPV6HINT)
+ .addRdata(TEST_SVC_PARAM_PORT)
+ .addRdata(TEST_SVC_PARAM_DOHPATH)
+ .build());
+ // Add answer for alpn doq.
+ os.write(new TestDnsRecordByteArrayBuilder()
+ .setNameCompression(true)
+ .setRRType(TYPE_SVCB)
+ .setTargetName(doqTargetName)
+ .setSvcPriority(2)
+ .addRdata(TEST_SVC_PARAM_ALPN_DOQ)
+ .addRdata(TEST_SVC_PARAM_IPV4HINT_2)
+ .build());
+ final DnsSvcbPacket pkt = DnsSvcbPacket.fromResponse(os.toByteArray());
+
+ assertTrue(pkt.isSupported("http/1.1"));
+ assertTrue(pkt.isSupported("h2"));
+ assertTrue(pkt.isSupported("doq"));
+ assertFalse(pkt.isSupported("http"));
+ assertFalse(pkt.isSupported("h3"));
+ assertFalse(pkt.isSupported(""));
+
+ assertEquals(dohTargetName, pkt.getTargetName("http/1.1"));
+ assertEquals(dohTargetName, pkt.getTargetName("h2"));
+ assertEquals(doqTargetName, pkt.getTargetName("doq"));
+ assertEquals(null, pkt.getTargetName("http"));
+ assertEquals(null, pkt.getTargetName("h3"));
+ assertEquals(null, pkt.getTargetName(""));
+
+ assertEquals(5353, pkt.getPort("http/1.1"));
+ assertEquals(5353, pkt.getPort("h2"));
+ assertEquals(-1, pkt.getPort("doq"));
+ assertEquals(-1, pkt.getPort("http"));
+ assertEquals(-1, pkt.getPort("h3"));
+ assertEquals(-1, pkt.getPort(""));
+
+ assertArrayEquals(expectedIpAddressesForHttps, pkt.getAddresses("http/1.1").toArray());
+ assertArrayEquals(expectedIpAddressesForHttps, pkt.getAddresses("h2").toArray());
+ assertArrayEquals(expectedIpAddressesForDoq, pkt.getAddresses("doq").toArray());
+ assertTrue(pkt.getAddresses("http").isEmpty());
+ assertTrue(pkt.getAddresses("h3").isEmpty());
+ assertTrue(pkt.getAddresses("").isEmpty());
+
+ assertEquals("/some-path{?dns}", pkt.getDohPath("http/1.1"));
+ assertEquals("/some-path{?dns}", pkt.getDohPath("h2"));
+ assertEquals("", pkt.getDohPath("doq"));
+ assertEquals(null, pkt.getDohPath("http"));
+ assertEquals(null, pkt.getDohPath("h3"));
+ assertEquals(null, pkt.getDohPath(""));
+ }
+
+ @Test
+ public void testDnsSvcbPacket_noIpHint() throws Exception {
+ final String targetName = "doq.dns.com";
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ os.write(makeDnsResponseHeaderAsByteArray(1 /* qdcount */, 1 /* ancount */, 0 /* nscount */,
+ 0 /* arcount */));
+ os.write(TEST_DNS_SVCB_QUESTION_SECTION);
+ // Add answer for alpn doq.
+ os.write(new TestDnsRecordByteArrayBuilder()
+ .setNameCompression(true)
+ .setRRType(TYPE_SVCB)
+ .setTargetName(targetName)
+ .addRdata(TEST_SVC_PARAM_ALPN_DOQ)
+ .build());
+ final DnsSvcbPacket pkt = DnsSvcbPacket.fromResponse(os.toByteArray());
+
+ assertTrue(pkt.isSupported("doq"));
+ assertEquals(targetName, pkt.getTargetName("doq"));
+ assertEquals(-1, pkt.getPort("doq"));
+ assertArrayEquals(new InetAddress[] {}, pkt.getAddresses("doq").toArray());
+ assertEquals("", pkt.getDohPath("doq"));
+ }
+
+ @Test
+ public void testDnsSvcbPacket_hasAnswerInAdditionalSection() throws Exception {
+ final InetAddress[] expectedIpAddresses = new InetAddress[] {
+ InetAddresses.parseNumericAddress("1.2.3.4"),
+ InetAddresses.parseNumericAddress("2001:db8::2"),
+ };
+
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ os.write(makeDnsResponseHeaderAsByteArray(1 /* qdcount */, 1 /* ancount */, 0 /* nscount */,
+ 2 /* arcount */));
+ os.write(TEST_DNS_SVCB_QUESTION_SECTION);
+ // Add SVCB record in the Answer section.
+ os.write(new TestDnsRecordByteArrayBuilder()
+ .setNameCompression(true)
+ .setRRType(TYPE_SVCB)
+ .setTargetName("doq.dns.com")
+ .addRdata(TEST_SVC_PARAM_ALPN_DOQ)
+ .addRdata(TEST_SVC_PARAM_IPV4HINT_2)
+ .addRdata(TEST_SVC_PARAM_IPV6HINT)
+ .build());
+ // Add A/AAAA records in the Additional section.
+ os.write(new TestDnsRecordByteArrayBuilder()
+ .setNameCompression(true)
+ .setRRType(TYPE_A)
+ .addRdata(InetAddresses.parseNumericAddress("1.2.3.4").getAddress())
+ .build());
+ os.write(new TestDnsRecordByteArrayBuilder()
+ .setNameCompression(true)
+ .setRRType(TYPE_AAAA)
+ .addRdata(InetAddresses.parseNumericAddress("2001:db8::2").getAddress())
+ .build());
+ final DnsSvcbPacket pkt = DnsSvcbPacket.fromResponse(os.toByteArray());
+
+ // If there are A/AAAA records in the Additional section, getAddresses() returns the IP
+ // addresses in those records instead of the IP addresses in ipv4hint/ipv6hint.
+ assertArrayEquals(expectedIpAddresses, pkt.getAddresses("doq").toArray());
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
index 8917ed3..ad30ce0 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
@@ -82,8 +82,8 @@
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
mSocketKey = new SocketKey(1000 /* interfaceIndex */);
- mSocketClient = new MdnsMultinetworkSocketClient(
- mHandlerThread.getLooper(), mProvider, mSharedLog);
+ mSocketClient = new MdnsMultinetworkSocketClient(mHandlerThread.getLooper(), mProvider,
+ mSharedLog, MdnsFeatureFlags.newBuilder().build());
mHandler.post(() -> mSocketClient.setCallback(mCallback));
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketReaderTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketReaderTests.java
index 19d8a00..37588b5 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketReaderTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketReaderTests.java
@@ -75,7 +75,7 @@
+ "the packet length");
} catch (IOException e) {
// Expected
- } catch (Exception e) {
+ } catch (RuntimeException e) {
fail(String.format(
Locale.ROOT,
"Should not have thrown any other exception except " + "for IOException: %s",
@@ -83,4 +83,4 @@
}
assertEquals(data.length, packetReader.getRemaining());
}
-}
\ No newline at end of file
+}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketTest.kt
index 28ea4b6..0877b68 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketTest.kt
@@ -27,6 +27,9 @@
@RunWith(DevSdkIgnoreRunner::class)
class MdnsPacketTest {
+ private fun makeFlags(isLabelCountLimitEnabled: Boolean = false): MdnsFeatureFlags =
+ MdnsFeatureFlags.newBuilder()
+ .setIsLabelCountLimitEnabled(isLabelCountLimitEnabled).build()
@Test
fun testParseQuery() {
// Probe packet with 1 question for Android.local, and 4 additionalRecords with 4 addresses
@@ -38,7 +41,7 @@
"010db8000000000000000000000789"
val bytes = HexDump.hexStringToByteArray(packetHex)
- val reader = MdnsPacketReader(bytes, bytes.size)
+ val reader = MdnsPacketReader(bytes, bytes.size, makeFlags())
val packet = MdnsPacket.parse(reader)
assertEquals(123, packet.transactionId)
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
index 3fc656a..a22e8c6 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
@@ -17,8 +17,10 @@
package com.android.server.connectivity.mdns;
import static android.net.InetAddresses.parseNumericAddress;
+
import static com.android.server.connectivity.mdns.util.MdnsUtils.Clock;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -337,7 +339,8 @@
packet.setSocketAddress(
new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
- final MdnsPacket parsedPacket = MdnsResponseDecoder.parseResponse(data6, data6.length);
+ final MdnsPacket parsedPacket = MdnsResponseDecoder.parseResponse(
+ data6, data6.length, MdnsFeatureFlags.newBuilder().build());
assertNotNull(parsedPacket);
final Network network = mock(Network.class);
@@ -636,7 +639,8 @@
private ArraySet<MdnsResponse> decode(MdnsResponseDecoder decoder, byte[] data,
Collection<MdnsResponse> existingResponses) throws MdnsPacket.ParseException {
- final MdnsPacket parsedPacket = MdnsResponseDecoder.parseResponse(data, data.length);
+ final MdnsPacket parsedPacket = MdnsResponseDecoder.parseResponse(
+ data, data.length, MdnsFeatureFlags.newBuilder().build());
assertNotNull(parsedPacket);
return new ArraySet<>(decoder.augmentResponses(parsedPacket,
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
index 74f1c37..8b7ab71 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
@@ -78,6 +78,7 @@
@Mock private SharedLog sharedLog;
private MdnsSocketClient mdnsClient;
+ private MdnsFeatureFlags flags = MdnsFeatureFlags.newBuilder().build();
@Before
public void setup() throws RuntimeException, IOException {
@@ -86,7 +87,7 @@
when(mockWifiManager.createMulticastLock(ArgumentMatchers.anyString()))
.thenReturn(mockMulticastLock);
- mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock, sharedLog) {
+ mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock, sharedLog, flags) {
@Override
MdnsSocket createMdnsSocket(int port, SharedLog sharedLog) throws IOException {
if (port == MdnsConstants.MDNS_PORT) {
@@ -515,7 +516,7 @@
//MdnsConfigsFlagsImpl.allowNetworkInterfaceIndexPropagation.override(true);
when(mockMulticastSocket.getInterfaceIndex()).thenReturn(21);
- mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock, sharedLog) {
+ mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock, sharedLog, flags) {
@Override
MdnsSocket createMdnsSocket(int port, SharedLog sharedLog) {
if (port == MdnsConstants.MDNS_PORT) {
@@ -538,7 +539,7 @@
//MdnsConfigsFlagsImpl.allowNetworkInterfaceIndexPropagation.override(false);
when(mockMulticastSocket.getInterfaceIndex()).thenReturn(21);
- mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock, sharedLog) {
+ mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock, sharedLog, flags) {
@Override
MdnsSocket createMdnsSocket(int port, SharedLog sharedLog) {
if (port == MdnsConstants.MDNS_PORT) {
diff --git a/thread/TEST_MAPPING b/thread/TEST_MAPPING
index 3eaebfa..30aeca5 100644
--- a/thread/TEST_MAPPING
+++ b/thread/TEST_MAPPING
@@ -3,5 +3,10 @@
{
"name": "CtsThreadNetworkTestCases"
}
+ ],
+ "postsubmit": [
+ {
+ "name": "ThreadNetworkUnitTests"
+ }
]
}