Merge "Allow HW address retrieval from EthernetNetworkFactory" into main
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 544ba01..9e0c970 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -137,8 +137,8 @@
private static final MacAddress NULL_MAC_ADDRESS = MacAddress.fromString("00:00:00:00:00:00");
private static final String TAG = "IpServer";
- private static final boolean DBG = false;
- private static final boolean VDBG = false;
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
private static final Class[] sMessageClasses = {
IpServer.class
};
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 873961a..d85d92f 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -173,8 +173,8 @@
public class Tethering {
private static final String TAG = Tethering.class.getSimpleName();
- private static final boolean DBG = false;
- private static final boolean VDBG = false;
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
private static final Class[] sMessageClasses = {
Tethering.class, TetherMainSM.class, IpServer.class
@@ -241,9 +241,6 @@
private final TetherMainSM mTetherMainSM;
private final OffloadController mOffloadController;
private final UpstreamNetworkMonitor mUpstreamNetworkMonitor;
- // TODO: Figure out how to merge this and other downstream-tracking objects
- // into a single coherent structure.
- private final HashSet<IpServer> mForwardedDownstreams;
private final VersionedBroadcastListener mCarrierConfigChange;
private final TetheringDependencies mDeps;
private final EntitlementManager mEntitlementMgr;
@@ -271,8 +268,6 @@
private boolean mRndisEnabled; // track the RNDIS function enabled state
private boolean mNcmEnabled; // track the NCM function enabled state
- // True iff. WiFi tethering should be started when soft AP is ready.
- private boolean mWifiTetherRequested;
private Network mTetherUpstream;
private TetherStatesParcel mTetherStatesParcel;
private boolean mDataSaverEnabled = false;
@@ -329,7 +324,6 @@
(what, obj) -> {
mTetherMainSM.sendMessage(TetherMainSM.EVENT_UPSTREAM_CALLBACK, what, 0, obj);
});
- mForwardedDownstreams = new HashSet<>();
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_CARRIER_CONFIG_CHANGED);
@@ -763,7 +757,6 @@
}
if ((enable && mgr.startTetheredHotspot(null /* use existing softap config */))
|| (!enable && mgr.stopSoftAp())) {
- mWifiTetherRequested = enable;
return TETHER_ERROR_NO_ERROR;
}
} finally {
@@ -1470,10 +1463,6 @@
}
private void disableWifiIpServing(String ifname, int apState) {
- // Regardless of whether we requested this transition, the AP has gone
- // down. Don't try to tether again unless we're requested to do so.
- mWifiTetherRequested = false;
-
mLog.log("Canceling WiFi tethering request - interface=" + ifname + " state=" + apState);
disableWifiIpServingCommon(TETHERING_WIFI, ifname);
@@ -1505,8 +1494,7 @@
private void enableWifiIpServing(String ifname, int wifiIpMode) {
mLog.log("request WiFi tethering - interface=" + ifname + " state=" + wifiIpMode);
- // Map wifiIpMode values to IpServer.Callback serving states, inferring
- // from mWifiTetherRequested as a final "best guess".
+ // Map wifiIpMode values to IpServer.Callback serving states.
final int ipServingMode;
switch (wifiIpMode) {
case IFACE_IP_MODE_TETHERED:
@@ -1653,11 +1641,6 @@
mLog.log(state.getName() + " got " + sMagicDecoderRing.get(what, Integer.toString(what)));
}
- private boolean upstreamWanted() {
- if (!mForwardedDownstreams.isEmpty()) return true;
- return mWifiTetherRequested;
- }
-
// Needed because the canonical source of upstream truth is just the
// upstream interface set, |mCurrentUpstreamIfaceSet|.
private boolean pertainsToCurrentUpstream(UpstreamNetworkState ns) {
@@ -1715,12 +1698,16 @@
private final ArrayList<IpServer> mNotifyList;
private final IPv6TetheringCoordinator mIPv6TetheringCoordinator;
private final OffloadWrapper mOffload;
+ // TODO: Figure out how to merge this and other downstream-tracking objects
+ // into a single coherent structure.
+ private final HashSet<IpServer> mForwardedDownstreams;
private static final int UPSTREAM_SETTLE_TIME_MS = 10000;
TetherMainSM(String name, Looper looper, TetheringDependencies deps) {
super(name, looper);
+ mForwardedDownstreams = new HashSet<>();
mInitialState = new InitialState();
mTetherModeAliveState = new TetherModeAliveState();
mSetIpForwardingEnabledErrorState = new SetIpForwardingEnabledErrorState();
@@ -2056,6 +2043,10 @@
}
}
+ private boolean upstreamWanted() {
+ return !mForwardedDownstreams.isEmpty();
+ }
+
class TetherModeAliveState extends State {
boolean mUpstreamWanted = false;
boolean mTryCell = true;
@@ -2393,6 +2384,9 @@
hasCallingPermission(NETWORK_SETTINGS)
|| hasCallingPermission(PERMISSION_MAINLINE_NETWORK_STACK)
|| hasCallingPermission(NETWORK_STACK);
+ if (callback == null) {
+ throw new NullPointerException();
+ }
mHandler.post(() -> {
mTetheringEventCallbacks.register(callback, new CallbackCookie(hasListPermission));
final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel();
@@ -2651,7 +2645,7 @@
}
pw.println(" - lastError = " + tetherState.lastError);
}
- pw.println("Upstream wanted: " + upstreamWanted());
+ pw.println("Upstream wanted: " + mTetherMainSM.upstreamWanted());
pw.println("Current upstream interface(s): " + mCurrentUpstreamIfaceSet);
pw.decreaseIndent();
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 9dfd225..3f86056 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -156,6 +156,7 @@
/**
* Get a reference to BluetoothAdapter to be used by tethering.
*/
+ @Nullable
public abstract BluetoothAdapter getBluetoothAdapter();
/**
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index aa73819..623f502 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -30,6 +30,7 @@
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.net.IIntResultListener;
@@ -377,7 +378,11 @@
@Override
public BluetoothAdapter getBluetoothAdapter() {
- return BluetoothAdapter.getDefaultAdapter();
+ final BluetoothManager btManager = getSystemService(BluetoothManager.class);
+ if (btManager == null) {
+ return null;
+ }
+ return btManager.getAdapter();
}
};
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index f01e1bb..9f430af 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -3653,10 +3653,9 @@
verify(mWifiManager).updateInterfaceIpState(TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
verifyNoMoreInteractions(mWifiManager);
-
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_TETHER);
- // FIXME: wifi tethering doesn't have upstream when P2P is enabled.
- verify(mUpstreamNetworkMonitor, never()).setTryCell(true);
+
+ verify(mUpstreamNetworkMonitor).setTryCell(true);
}
// TODO: Test that a request for hotspot mode doesn't interfere with an
diff --git a/framework-t/src/android/net/nsd/NsdServiceInfo.java b/framework-t/src/android/net/nsd/NsdServiceInfo.java
index dba08a1..9491a9c 100644
--- a/framework-t/src/android/net/nsd/NsdServiceInfo.java
+++ b/framework-t/src/android/net/nsd/NsdServiceInfo.java
@@ -16,6 +16,8 @@
package android.net.nsd;
+import static com.android.net.module.util.HexDump.toHexString;
+
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -39,6 +41,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.StringJoiner;
/**
* A class representing service information for network service discovery
@@ -541,11 +544,50 @@
.append(", network: ").append(mNetwork)
.append(", expirationTime: ").append(mExpirationTime);
- byte[] txtRecord = getTxtRecord();
- sb.append(", txtRecord: ").append(new String(txtRecord, StandardCharsets.UTF_8));
+ final StringJoiner txtJoiner =
+ new StringJoiner(", " /* delimiter */, "{" /* prefix */, "}" /* suffix */);
+
+ sb.append(", txtRecord: ");
+ for (int i = 0; i < mTxtRecord.size(); i++) {
+ txtJoiner.add(mTxtRecord.keyAt(i) + "=" + getPrintableTxtValue(mTxtRecord.valueAt(i)));
+ }
+ sb.append(txtJoiner.toString());
return sb.toString();
}
+ /**
+ * Returns printable string for {@code txtValue}.
+ *
+ * If {@code txtValue} contains non-printable ASCII characters, a HEX string with prefix "0x"
+ * will be returned. Otherwise, the ASCII string of {@code txtValue} is returned.
+ *
+ */
+ private static String getPrintableTxtValue(@Nullable byte[] txtValue) {
+ if (txtValue == null) {
+ return "(null)";
+ }
+
+ if (containsNonPrintableChars(txtValue)) {
+ return "0x" + toHexString(txtValue);
+ }
+
+ return new String(txtValue, StandardCharsets.US_ASCII);
+ }
+
+ /**
+ * Returns {@code true} if {@code txtValue} contains non-printable ASCII characters.
+ *
+ * The printable characters are in range of [32, 126].
+ */
+ private static boolean containsNonPrintableChars(byte[] txtValue) {
+ for (int i = 0; i < txtValue.length; i++) {
+ if (txtValue[i] < 32 || txtValue[i] > 126) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/** Implement the Parcelable interface */
public int describeContents() {
return 0;
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 005d617..b1ae019 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -478,6 +478,7 @@
private volatile boolean mLockdownEnabled;
private final boolean mRequestRestrictedWifiEnabled;
+ private final boolean mBackgroundFirewallChainEnabled;
/**
* Stale copy of uid blocked reasons provided by NPMS. As long as they are accessed only in
@@ -1798,6 +1799,8 @@
mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
mRequestRestrictedWifiEnabled = mDeps.isAtLeastU()
&& mDeps.isFeatureEnabled(context, REQUEST_RESTRICTED_WIFI);
+ mBackgroundFirewallChainEnabled = mDeps.isAtLeastV() && mDeps.isFeatureNotChickenedOut(
+ context, ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN);
mCarrierPrivilegeAuthenticator = mDeps.makeCarrierPrivilegeAuthenticator(
mContext, mTelephonyManager, mRequestRestrictedWifiEnabled,
this::handleUidCarrierPrivilegesLost, mHandler);
@@ -4152,6 +4155,9 @@
pw.println();
pw.println("Multicast routing supported: " +
(mMulticastRoutingCoordinatorService != null));
+
+ pw.println();
+ pw.println("Background firewall chain enabled: " + mBackgroundFirewallChainEnabled);
}
private void dumpNetworks(IndentingPrintWriter pw) {
@@ -13521,6 +13527,12 @@
public void setUidFirewallRule(final int chain, final int uid, final int rule) {
enforceNetworkStackOrSettingsPermission();
+ if (chain == FIREWALL_CHAIN_BACKGROUND && !mBackgroundFirewallChainEnabled) {
+ Log.i(TAG, "Ignoring operation setUidFirewallRule on the background chain because the"
+ + " feature is disabled.");
+ return;
+ }
+
// There are only two type of firewall rule: FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
int firewallRule = getFirewallRuleType(chain, rule);
@@ -13593,6 +13605,12 @@
public void setFirewallChainEnabled(final int chain, final boolean enable) {
enforceNetworkStackOrSettingsPermission();
+ if (chain == FIREWALL_CHAIN_BACKGROUND && !mBackgroundFirewallChainEnabled) {
+ Log.i(TAG, "Ignoring operation setFirewallChainEnabled on the background chain because"
+ + " the feature is disabled.");
+ return;
+ }
+
try {
mBpfNetMaps.setChildChain(chain, enable);
} catch (ServiceSpecificException e) {
@@ -13619,6 +13637,12 @@
public void replaceFirewallChain(final int chain, final int[] uids) {
enforceNetworkStackOrSettingsPermission();
+ if (chain == FIREWALL_CHAIN_BACKGROUND && !mBackgroundFirewallChainEnabled) {
+ Log.i(TAG, "Ignoring operation replaceFirewallChain on the background chain because"
+ + " the feature is disabled.");
+ return;
+ }
+
mBpfNetMaps.replaceUidChain(chain, uids);
}
diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java
index a55c683..176307d 100644
--- a/service/src/com/android/server/connectivity/ConnectivityFlags.java
+++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java
@@ -42,6 +42,8 @@
public static final String INGRESS_TO_VPN_ADDRESS_FILTERING =
"ingress_to_vpn_address_filtering";
+ public static final String BACKGROUND_FIREWALL_CHAIN = "background_firewall_chain";
+
private boolean mNoRematchAllRequestsOnRegister;
/**
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NdOption.java b/staticlibs/device/com/android/net/module/util/netlink/NdOption.java
index defc88a..4f58380 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NdOption.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NdOption.java
@@ -67,6 +67,9 @@
case StructNdOptRdnss.TYPE:
return StructNdOptRdnss.parse(buf);
+ case StructNdOptPio.TYPE:
+ return StructNdOptPio.parse(buf);
+
default:
int newPosition = Math.min(buf.limit(), buf.position() + length * 8);
buf.position(newPosition);
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNdOptPio.java b/staticlibs/device/com/android/net/module/util/netlink/StructNdOptPio.java
new file mode 100644
index 0000000..65541eb
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNdOptPio.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.netlink;
+
+import android.net.IpPrefix;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.HexDump;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.PrefixInformationOption;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.Objects;
+
+/**
+ * The Prefix Information Option. RFC 4861.
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Type | Length | Prefix Length |L|A|R|P| Rsvd1 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Valid Lifetime |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Preferred Lifetime |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Reserved2 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | |
+ * + +
+ * | |
+ * + Prefix +
+ * | |
+ * + +
+ * | |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class StructNdOptPio extends NdOption {
+ private static final String TAG = StructNdOptPio.class.getSimpleName();
+ public static final int TYPE = 3;
+ public static final byte LENGTH = 4; // Length in 8-byte units
+
+ public final byte flags;
+ public final long preferred;
+ public final long valid;
+ @NonNull
+ public final IpPrefix prefix;
+
+ public StructNdOptPio(byte flags, long preferred, long valid, @NonNull final IpPrefix prefix) {
+ super((byte) TYPE, LENGTH);
+ this.prefix = Objects.requireNonNull(prefix, "prefix must not be null");
+ this.flags = flags;
+ this.preferred = preferred;
+ this.valid = valid;
+ }
+
+ /**
+ * Parses a PrefixInformation option from a {@link ByteBuffer}.
+ *
+ * @param buf The buffer from which to parse the option. The buffer's byte order must be
+ * {@link java.nio.ByteOrder#BIG_ENDIAN}.
+ * @return the parsed option, or {@code null} if the option could not be parsed successfully.
+ */
+ public static StructNdOptPio parse(@NonNull ByteBuffer buf) {
+ if (buf == null || buf.remaining() < LENGTH * 8) return null;
+ try {
+ final PrefixInformationOption pio = Struct.parse(PrefixInformationOption.class, buf);
+ if (pio.type != TYPE) {
+ throw new IllegalArgumentException("Invalid type " + pio.type);
+ }
+ if (pio.length != LENGTH) {
+ throw new IllegalArgumentException("Invalid length " + pio.length);
+ }
+ return new StructNdOptPio(pio.flags, pio.preferredLifetime, pio.validLifetime,
+ pio.getIpPrefix());
+ } catch (IllegalArgumentException | BufferUnderflowException e) {
+ // Not great, but better than throwing an exception that might crash the caller.
+ // Convention in this package is that null indicates that the option was truncated
+ // or malformed, so callers must already handle it.
+ Log.d(TAG, "Invalid PIO option: " + e);
+ return null;
+ }
+ }
+
+ protected void writeToByteBuffer(ByteBuffer buf) {
+ buf.put(PrefixInformationOption.build(prefix, flags, valid, preferred));
+ }
+
+ /** Outputs the wire format of the option to a new big-endian ByteBuffer. */
+ public ByteBuffer toByteBuffer() {
+ final ByteBuffer buf = ByteBuffer.allocate(Struct.getSize(PrefixInformationOption.class));
+ writeToByteBuffer(buf);
+ buf.flip();
+ return buf;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return String.format("NdOptPio(flags:%s, preferred lft:%s, valid lft:%s, prefix:%s)",
+ HexDump.toHexString(flags), preferred, valid, prefix);
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java b/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java
index 0fc85e4..bbbe571 100644
--- a/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java
+++ b/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java
@@ -24,9 +24,13 @@
import androidx.annotation.VisibleForTesting;
import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Computed;
import com.android.net.module.util.Struct.Field;
import com.android.net.module.util.Struct.Type;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -72,6 +76,9 @@
@Field(order = 7, type = Type.ByteArray, arraysize = 16)
public final byte[] prefix;
+ @Computed
+ private final IpPrefix mIpPrefix;
+
@VisibleForTesting
public PrefixInformationOption(final byte type, final byte length, final byte prefixLen,
final byte flags, final long validLifetime, final long preferredLifetime,
@@ -84,6 +91,23 @@
this.preferredLifetime = preferredLifetime;
this.reserved = reserved;
this.prefix = prefix;
+
+ try {
+ final Inet6Address addr = (Inet6Address) InetAddress.getByAddress(prefix);
+ mIpPrefix = new IpPrefix(addr, prefixLen);
+ } catch (UnknownHostException | ClassCastException e) {
+ // UnknownHostException should never happen unless prefix is null.
+ // ClassCastException can occur when prefix is an IPv6 mapped IPv4 address.
+ // Both scenarios should throw an exception in the context of Struct#parse().
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Return the prefix {@link IpPrefix} included in the PIO.
+ */
+ public IpPrefix getIpPrefix() {
+ return mIpPrefix;
}
/**
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPioTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPioTest.java
new file mode 100644
index 0000000..0d88829
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPioTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.netlink;
+
+import static com.android.net.module.util.NetworkStackConstants.INFINITE_LEASE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.net.IpPrefix;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.structs.PrefixInformationOption;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StructNdOptPioTest {
+ private static final IpPrefix TEST_PREFIX = new IpPrefix("2a00:79e1:abc:f605::/64");
+ private static final byte TEST_PIO_FLAGS_P_UNSET = (byte) 0xC0; // L=1,A=1
+ private static final byte TEST_PIO_FLAGS_P_SET = (byte) 0xD0; // L=1,A=1,P=1
+ private static final String PIO_BYTES =
+ "0304" // type=3, length=4
+ + "40" // prefix length=64
+ + "C0" // L=1,A=1
+ + "00278D00" // valid=259200
+ + "00093A80" // preferred=604800
+ + "00000000" // Reserved2
+ + "2A0079E10ABCF6050000000000000000"; // prefix=2a00:79e1:abc:f605::
+
+ private static final String PIO_WITH_P_FLAG_BYTES =
+ "0304" // type=3, length=4
+ + "40" // prefix length=64
+ + "D0" // L=1,A=1,P=1
+ + "00278D00" // valid=2592000
+ + "00093A80" // preferred=604800
+ + "00000000" // Reserved2
+ + "2A0079E10ABCF6050000000000000000"; // prefix=2a00:79e1:abc:f605::
+
+ private static final String PIO_WITH_P_FLAG_INFINITY_LIFETIME_BYTES =
+ "0304" // type=3, length=4
+ + "40" // prefix length=64
+ + "D0" // L=1,A=1,P=1
+ + "FFFFFFFF" // valid=infinity
+ + "FFFFFFFF" // preferred=infintiy
+ + "00000000" // Reserved2
+ + "2A0079E10ABCF6050000000000000000"; // prefix=2a00:79e1:abc:f605::
+
+ private static void assertPioOptMatches(final StructNdOptPio opt, int length, byte flags,
+ long preferred, long valid, final IpPrefix prefix) {
+ assertEquals(StructNdOptPio.TYPE, opt.type);
+ assertEquals(length, opt.length);
+ assertEquals(flags, opt.flags);
+ assertEquals(preferred, opt.preferred);
+ assertEquals(valid, opt.valid);
+ assertEquals(prefix, opt.prefix);
+ }
+
+ private static void assertToByteBufferMatches(final StructNdOptPio opt, final String expected) {
+ String actual = HexEncoding.encodeToString(opt.toByteBuffer().array());
+ assertEquals(expected, actual);
+ }
+
+ private static void doPioParsingTest(final String optionHexString, int length, byte flags,
+ long preferred, long valid, final IpPrefix prefix) {
+ final byte[] rawBytes = HexEncoding.decode(optionHexString);
+ final StructNdOptPio opt = StructNdOptPio.parse(ByteBuffer.wrap(rawBytes));
+ assertPioOptMatches(opt, length, flags, preferred, valid, prefix);
+ assertToByteBufferMatches(opt, optionHexString);
+ }
+
+ @Test
+ public void testParsingPioWithoutPFlag() {
+ doPioParsingTest(PIO_BYTES, 4 /* length */, TEST_PIO_FLAGS_P_UNSET,
+ 604800 /* preferred */, 2592000 /* valid */, TEST_PREFIX);
+ }
+
+ @Test
+ public void testParsingPioWithPFlag() {
+ doPioParsingTest(PIO_WITH_P_FLAG_BYTES, 4 /* length */, TEST_PIO_FLAGS_P_SET,
+ 604800 /* preferred */, 2592000 /* valid */, TEST_PREFIX);
+ }
+
+ @Test
+ public void testParsingPioWithPFlag_infinityLifetime() {
+ doPioParsingTest(PIO_WITH_P_FLAG_INFINITY_LIFETIME_BYTES, 4 /* length */,
+ TEST_PIO_FLAGS_P_SET,
+ Integer.toUnsignedLong(INFINITE_LEASE) /* preferred */,
+ Integer.toUnsignedLong(INFINITE_LEASE) /* valid */,
+ TEST_PREFIX);
+ }
+
+ @Test
+ public void testToByteBuffer() {
+ final StructNdOptPio pio =
+ new StructNdOptPio(TEST_PIO_FLAGS_P_UNSET, 604800 /* preferred */,
+ 2592000 /* valid */, TEST_PREFIX);
+ assertToByteBufferMatches(pio, PIO_BYTES);
+ }
+
+ @Test
+ public void testToByteBuffer_withPFlag() {
+ final StructNdOptPio pio =
+ new StructNdOptPio(TEST_PIO_FLAGS_P_SET, 604800 /* preferred */,
+ 2592000 /* valid */, TEST_PREFIX);
+ assertToByteBufferMatches(pio, PIO_WITH_P_FLAG_BYTES);
+ }
+
+ @Test
+ public void testToByteBuffer_infinityLifetime() {
+ final StructNdOptPio pio =
+ new StructNdOptPio(TEST_PIO_FLAGS_P_SET,
+ Integer.toUnsignedLong(INFINITE_LEASE) /* preferred */,
+ Integer.toUnsignedLong(INFINITE_LEASE) /* valid */, TEST_PREFIX);
+ assertToByteBufferMatches(pio, PIO_WITH_P_FLAG_INFINITY_LIFETIME_BYTES);
+ }
+
+ private static ByteBuffer makePioOption(byte type, byte length, byte prefixLen, byte flags,
+ long valid, long preferred, final byte[] prefix) {
+ final PrefixInformationOption pio = new PrefixInformationOption(type, length, prefixLen,
+ flags, valid, preferred, 0 /* reserved */, prefix);
+ return ByteBuffer.wrap(pio.writeToBytes(ByteOrder.BIG_ENDIAN));
+ }
+
+ @Test
+ public void testParsing_invalidOptionType() {
+ final ByteBuffer buf = makePioOption((byte) 24 /* wrong type:RIO */,
+ (byte) 4 /* length */, (byte) 64 /* prefixLen */, TEST_PIO_FLAGS_P_SET,
+ 2592000 /* valid */, 604800 /* preferred */, TEST_PREFIX.getRawAddress());
+ assertNull(StructNdOptPio.parse(buf));
+ }
+
+ @Test
+ public void testParsing_invalidOptionLength() {
+ final ByteBuffer buf = makePioOption((byte) 24 /* wrong type:RIO */,
+ (byte) 3 /* wrong length */, (byte) 64 /* prefixLen */,
+ TEST_PIO_FLAGS_P_SET, 2592000 /* valid */, 604800 /* preferred */,
+ TEST_PREFIX.getRawAddress());
+ assertNull(StructNdOptPio.parse(buf));
+ }
+
+ @Test
+ public void testParsing_truncatedByteBuffer() {
+ final ByteBuffer buf = makePioOption((byte) 3 /* type */, (byte) 4 /* length */,
+ (byte) 64 /* prefixLen */, TEST_PIO_FLAGS_P_SET,
+ 2592000 /* valid */, 604800 /* preferred */, TEST_PREFIX.getRawAddress());
+ final int len = buf.limit();
+ for (int i = 0; i < buf.limit() - 1; i++) {
+ buf.flip();
+ buf.limit(i);
+ assertNull("Option truncated to " + i + " bytes, should have returned null",
+ StructNdOptPio.parse(buf));
+ }
+ buf.flip();
+ buf.limit(len);
+
+ final StructNdOptPio opt = StructNdOptPio.parse(buf);
+ assertPioOptMatches(opt, (byte) 4 /* length */, TEST_PIO_FLAGS_P_SET,
+ 604800 /* preferred */, 2592000 /* valid */, TEST_PREFIX);
+ }
+
+ @Test
+ public void testParsing_invalidByteBufferLength() {
+ final ByteBuffer buf = makePioOption((byte) 3 /* type */, (byte) 4 /* length */,
+ (byte) 64 /* prefixLen */, TEST_PIO_FLAGS_P_SET,
+ 2592000 /* valid */, 604800 /* preferred */, TEST_PREFIX.getRawAddress());
+ buf.limit(31); // less than 4 * 8
+ assertNull(StructNdOptPio.parse(buf));
+ }
+
+ @Test
+ public void testToString() {
+ final ByteBuffer buf = makePioOption((byte) 3 /* type */, (byte) 4 /* length */,
+ (byte) 64 /* prefixLen */, TEST_PIO_FLAGS_P_SET,
+ 2592000 /* valid */, 604800 /* preferred */, TEST_PREFIX.getRawAddress());
+ final StructNdOptPio opt = StructNdOptPio.parse(buf);
+ final String expected = "NdOptPio"
+ + "(flags:D0, preferred lft:604800, valid lft:2592000,"
+ + " prefix:2a00:79e1:abc:f605::/64)";
+ assertEquals(expected, opt.toString());
+ }
+}
diff --git a/tests/cts/OWNERS b/tests/cts/OWNERS
index cb4ca59..acf506d 100644
--- a/tests/cts/OWNERS
+++ b/tests/cts/OWNERS
@@ -9,4 +9,6 @@
# For incremental changes on EthernetManagerTest to increase coverage for existing behavior and for
# testing bug fixes.
per-file net/src/android/net/cts/EthernetManagerTest.kt = prohr@google.com #{LAST_RESORT_SUGGESTION}
+# Temporary ownership to develop APF CTS tests.
+per-file net/src/android/net/cts/ApfIntegrationTest.kt = prohr@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
index 3ecbdc6..68766e6 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -15,37 +15,62 @@
*/
package android.net.cts
+import android.Manifest.permission.WRITE_DEVICE_CONFIG
import android.content.pm.PackageManager.FEATURE_WIFI
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.NetworkRequest
+import android.net.apf.ApfCapabilities
import android.os.Build
import android.platform.test.annotations.AppModeFull
+import android.provider.DeviceConfig
+import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
import android.system.OsConstants
import androidx.test.platform.app.InstrumentationRegistry
import com.android.compatibility.common.util.PropertyUtil.isVendorApiLevelNewerThan
-import com.android.compatibility.common.util.SystemUtil
+import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.NetworkStackModuleTest
import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.runAsShell
import com.google.common.truth.Truth.assertThat
-import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import org.junit.After
import org.junit.Assume.assumeTrue
import org.junit.Before
+import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
private const val TIMEOUT_MS = 2000L
+private const val APF_NEW_RA_FILTER_VERSION = "apf_new_ra_filter_version"
@AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
@RunWith(DevSdkIgnoreRunner::class)
@NetworkStackModuleTest
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
class ApfIntegrationTest {
+ companion object {
+ @BeforeClass
+ @JvmStatic
+ fun setupOnce() {
+ // TODO: check that there is no active wifi network. Otherwise, ApfFilter has already been
+ // created.
+ // APF adb cmds are only implemented in ApfFilter.java. Enable experiment to prevent
+ // LegacyApfFilter.java from being used.
+ runAsShell(WRITE_DEVICE_CONFIG) {
+ DeviceConfig.setProperty(
+ NAMESPACE_CONNECTIVITY,
+ APF_NEW_RA_FILTER_VERSION,
+ "1", // value => force enabled
+ false // makeDefault
+ )
+ }
+ }
+ }
+
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
private val cm by lazy { context.getSystemService(ConnectivityManager::class.java)!! }
private val pm by lazy { context.packageManager }
@@ -68,6 +93,7 @@
ifname = assertNotNull(it.lp.interfaceName)
true
}
+ runShellCommandOrThrow("cmd network_stack apf $ifname pause")
}
@After
@@ -75,17 +101,25 @@
if (::networkCallback.isInitialized) {
cm.unregisterNetworkCallback(networkCallback)
}
+ if (::ifname.isInitialized) {
+ runShellCommandOrThrow("cmd network_stack apf $ifname resume")
+ }
+ }
+
+ fun getApfCapabilities(): ApfCapabilities {
+ val caps = runShellCommandOrThrow("cmd network_stack apf $ifname capabilities").trim()
+ val (version, maxLen, packetFormat) = caps.split(",").map { it.toInt() }
+ return ApfCapabilities(version, maxLen, packetFormat)
}
@Test
fun testGetApfCapabilities() {
- val caps = SystemUtil.runShellCommand("cmd network_stack apf $ifname capabilities").trim()
- val (version, maxLen, packetFormat) = caps.split(",").map { it.toInt() }
- assertEquals(4, version)
- assertThat(maxLen).isAtLeast(1024)
+ val caps = getApfCapabilities()
+ assertThat(caps.apfVersionSupported).isEqualTo(4)
+ assertThat(caps.maximumApfProgramSize).isAtLeast(1024)
if (isVendorApiLevelNewerThan(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) {
- assertThat(maxLen).isAtLeast(2000)
+ assertThat(caps.maximumApfProgramSize).isAtLeast(2000)
}
- assertEquals(OsConstants.ARPHRD_ETHER, packetFormat)
+ assertThat(caps.apfPacketFormat).isEqualTo(OsConstants.ARPHRD_ETHER)
}
}
diff --git a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
index bca18f5..7ab73c2 100644
--- a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
+++ b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
@@ -16,10 +16,14 @@
package android.net.cts;
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
+import static android.content.pm.PackageManager.FEATURE_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
@@ -34,17 +38,21 @@
import android.platform.test.annotations.AppModeFull;
import android.system.ErrnoException;
import android.system.OsConstants;
+import android.util.ArraySet;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.testutils.DeviceConfigRule;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Set;
+
@RunWith(AndroidJUnit4.class)
public class MultinetworkApiTest {
@Rule
@@ -74,9 +82,8 @@
private ContentResolver mCR;
private ConnectivityManager mCM;
private CtsNetUtils mCtsNetUtils;
- private String mOldMode;
- private String mOldDnsSpecifier;
private Context mContext;
+ private Network mRequestedCellNetwork;
@Before
public void setUp() throws Exception {
@@ -86,9 +93,16 @@
mCtsNetUtils = new CtsNetUtils(mContext);
}
+ @After
+ public void tearDown() {
+ if (mCtsNetUtils.cellConnectAttempted()) {
+ mCtsNetUtils.disconnectFromCell();
+ }
+ }
+
@Test
- public void testGetaddrinfo() throws ErrnoException {
- for (Network network : mCtsNetUtils.getTestableNetworks()) {
+ public void testGetaddrinfo() throws Exception {
+ for (Network network : getTestableNetworks()) {
int errno = runGetaddrinfoCheck(network.getNetworkHandle());
if (errno != 0) {
throw new ErrnoException(
@@ -99,12 +113,12 @@
@Test
@AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
- public void testSetprocnetwork() throws ErrnoException {
+ public void testSetprocnetwork() throws Exception {
// Hopefully no prior test in this process space has set a default network.
assertNull(mCM.getProcessDefaultNetwork());
assertEquals(0, NetworkUtils.getBoundNetworkForProcess());
- for (Network network : mCtsNetUtils.getTestableNetworks()) {
+ for (Network network : getTestableNetworks()) {
mCM.setProcessDefaultNetwork(null);
assertNull(mCM.getProcessDefaultNetwork());
@@ -123,7 +137,7 @@
mCM.setProcessDefaultNetwork(null);
}
- for (Network network : mCtsNetUtils.getTestableNetworks()) {
+ for (Network network : getTestableNetworks()) {
NetworkUtils.bindProcessToNetwork(0);
assertNull(mCM.getBoundNetworkForProcess());
@@ -143,8 +157,8 @@
@Test
@AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
- public void testSetsocknetwork() throws ErrnoException {
- for (Network network : mCtsNetUtils.getTestableNetworks()) {
+ public void testSetsocknetwork() throws Exception {
+ for (Network network : getTestableNetworks()) {
int errno = runSetsocknetwork(network.getNetworkHandle());
if (errno != 0) {
throw new ErrnoException(
@@ -154,8 +168,8 @@
}
@Test
- public void testNativeDatagramTransmission() throws ErrnoException {
- for (Network network : mCtsNetUtils.getTestableNetworks()) {
+ public void testNativeDatagramTransmission() throws Exception {
+ for (Network network : getTestableNetworks()) {
int errno = runDatagramCheck(network.getNetworkHandle());
if (errno != 0) {
throw new ErrnoException(
@@ -165,7 +179,7 @@
}
@Test
- public void testNoSuchNetwork() {
+ public void testNoSuchNetwork() throws Exception {
final Network eNoNet = new Network(54321);
assertNull(mCM.getNetworkInfo(eNoNet));
@@ -178,9 +192,9 @@
}
@Test
- public void testNetworkHandle() {
+ public void testNetworkHandle() throws Exception {
// Test Network -> NetworkHandle -> Network results in the same Network.
- for (Network network : mCtsNetUtils.getTestableNetworks()) {
+ for (Network network : getTestableNetworks()) {
long networkHandle = network.getNetworkHandle();
Network newNetwork = Network.fromNetworkHandle(networkHandle);
assertEquals(newNetwork, network);
@@ -203,9 +217,7 @@
@Test
public void testResNApi() throws Exception {
- final Network[] testNetworks = mCtsNetUtils.getTestableNetworks();
-
- for (Network network : testNetworks) {
+ for (Network network : getTestableNetworks()) {
// Throws AssertionError directly in jni function if test fail.
runResNqueryCheck(network.getNetworkHandle());
runResNsendCheck(network.getNetworkHandle());
@@ -241,7 +253,7 @@
// b/144521720
try {
mCtsNetUtils.setPrivateDnsStrictMode(GOOGLE_PRIVATE_DNS_SERVER);
- for (Network network : mCtsNetUtils.getTestableNetworks()) {
+ for (Network network : getTestableNetworks()) {
// Wait for private DNS setting to propagate.
mCtsNetUtils.awaitPrivateDnsSetting("NxDomain test wait private DNS setting timeout",
network, GOOGLE_PRIVATE_DNS_SERVER, true);
@@ -251,4 +263,44 @@
mCtsNetUtils.restorePrivateDnsSetting();
}
}
+
+ /**
+ * Get all testable Networks with internet capability.
+ */
+ private Set<Network> getTestableNetworks() throws InterruptedException {
+ // Obtain cell and Wi-Fi through CtsNetUtils (which uses NetworkCallbacks), as they may have
+ // just been reconnected by the test using NetworkCallbacks, so synchronous calls may not
+ // yet return them (synchronous calls and callbacks should not be mixed for a given
+ // Network).
+ final Set<Network> testableNetworks = new ArraySet<>();
+ if (mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
+ if (!mCtsNetUtils.cellConnectAttempted()) {
+ mRequestedCellNetwork = mCtsNetUtils.connectToCell();
+ }
+ assertNotNull("Cell network requested but not obtained", mRequestedCellNetwork);
+ testableNetworks.add(mRequestedCellNetwork);
+ }
+
+ if (mContext.getPackageManager().hasSystemFeature(FEATURE_WIFI)) {
+ testableNetworks.add(mCtsNetUtils.ensureWifiConnected());
+ }
+
+ // Obtain other networks through the synchronous API, if any.
+ for (Network network : mCtsNetUtils.getTestableNetworks()) {
+ final NetworkCapabilities nc = mCM.getNetworkCapabilities(network);
+ if (nc != null
+ && !nc.hasTransport(TRANSPORT_WIFI)
+ && !nc.hasTransport(TRANSPORT_CELLULAR)) {
+ testableNetworks.add(network);
+ }
+ }
+
+ // In practice this should not happen as getTestableNetworks throws if there is no network
+ // at all.
+ assertFalse("This device does not support WiFi nor cell data, and does not have any other "
+ + "network connected. This test requires at least one internet-providing "
+ + "network.",
+ testableNetworks.isEmpty());
+ return testableNetworks;
+ }
}
diff --git a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.kt b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.kt
new file mode 100644
index 0000000..8f86f06
--- /dev/null
+++ b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.nsd
+
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import kotlin.test.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Unit tests for {@link NsdServiceInfo}. */
+@SmallTest
+@ConnectivityModuleTest
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class NsdServiceInfoTest {
+ @Test
+ fun testToString_txtRecord() {
+ val info = NsdServiceInfo().apply {
+ this.setAttribute("abc", byteArrayOf(0xff.toByte(), 0xfe.toByte()))
+ this.setAttribute("def", null as String?)
+ this.setAttribute("ghi", "猫")
+ this.setAttribute("jkl", byteArrayOf(0, 0x21))
+ this.setAttribute("mno", "Hey Tom! It's you?.~{}")
+ }
+
+ val infoStr = info.toString()
+
+ assertTrue(
+ infoStr.contains("txtRecord: " +
+ "{abc=0xFFFE, def=(null), ghi=0xE78CAB, jkl=0x0021, mno=Hey Tom! It's you?.~{}}"),
+ infoStr)
+ }
+}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 17c5901..7822fe0 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -172,6 +172,7 @@
import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType;
import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackRegister;
import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister;
+import static com.android.server.connectivity.ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN;
import static com.android.server.connectivity.ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING;
import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.ConcurrentUtils.await;
@@ -391,6 +392,7 @@
import com.android.internal.util.WakeupMessage;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.ArrayTrackRecord;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
import com.android.net.module.util.CollectionUtils;
@@ -2171,6 +2173,8 @@
return true;
case INGRESS_TO_VPN_ADDRESS_FILTERING:
return true;
+ case BACKGROUND_FIREWALL_CHAIN:
+ return true;
default:
return super.isFeatureNotChickenedOut(context, name);
}
@@ -10488,7 +10492,10 @@
doTestSetUidFirewallRule(FIREWALL_CHAIN_POWERSAVE, FIREWALL_RULE_DENY);
doTestSetUidFirewallRule(FIREWALL_CHAIN_RESTRICTED, FIREWALL_RULE_DENY);
doTestSetUidFirewallRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, FIREWALL_RULE_DENY);
- doTestSetUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, FIREWALL_RULE_DENY);
+ if (SdkLevel.isAtLeastV()) {
+ // FIREWALL_CHAIN_BACKGROUND is only available on V+.
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, FIREWALL_RULE_DENY);
+ }
doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_1, FIREWALL_RULE_ALLOW);
doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_2, FIREWALL_RULE_ALLOW);
doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_3, FIREWALL_RULE_ALLOW);
@@ -10496,16 +10503,19 @@
@Test @IgnoreUpTo(SC_V2)
public void testSetFirewallChainEnabled() throws Exception {
- final List<Integer> firewallChains = Arrays.asList(
+ final List<Integer> firewallChains = new ArrayList<>(Arrays.asList(
FIREWALL_CHAIN_DOZABLE,
FIREWALL_CHAIN_STANDBY,
FIREWALL_CHAIN_POWERSAVE,
FIREWALL_CHAIN_RESTRICTED,
FIREWALL_CHAIN_LOW_POWER_STANDBY,
- FIREWALL_CHAIN_BACKGROUND,
FIREWALL_CHAIN_OEM_DENY_1,
FIREWALL_CHAIN_OEM_DENY_2,
- FIREWALL_CHAIN_OEM_DENY_3);
+ FIREWALL_CHAIN_OEM_DENY_3));
+ if (SdkLevel.isAtLeastV()) {
+ // FIREWALL_CHAIN_BACKGROUND is only available on V+.
+ firewallChains.add(FIREWALL_CHAIN_BACKGROUND);
+ }
for (final int chain: firewallChains) {
mCm.setFirewallChainEnabled(chain, true /* enabled */);
verify(mBpfNetMaps).setChildChain(chain, true /* enable */);
@@ -10552,7 +10562,10 @@
doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_POWERSAVE, allowlist);
doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_RESTRICTED, allowlist);
doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_LOW_POWER_STANDBY, allowlist);
- doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_BACKGROUND, allowlist);
+ if (SdkLevel.isAtLeastV()) {
+ // FIREWALL_CHAIN_BACKGROUND is only available on V+.
+ doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_BACKGROUND, allowlist);
+ }
doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_STANDBY, denylist);
doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_OEM_DENY_1, denylist);
@@ -10574,7 +10587,10 @@
doTestReplaceFirewallChain(FIREWALL_CHAIN_POWERSAVE);
doTestReplaceFirewallChain(FIREWALL_CHAIN_RESTRICTED);
doTestReplaceFirewallChain(FIREWALL_CHAIN_LOW_POWER_STANDBY);
- doTestReplaceFirewallChain(FIREWALL_CHAIN_BACKGROUND);
+ if (SdkLevel.isAtLeastV()) {
+ // FIREWALL_CHAIN_BACKGROUND is only available on V+.
+ doTestReplaceFirewallChain(FIREWALL_CHAIN_BACKGROUND);
+ }
doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_1);
doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_2);
doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_3);
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSFirewallChainTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSFirewallChainTest.kt
new file mode 100644
index 0000000..16de4da
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSFirewallChainTest.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server
+
+import android.net.ConnectivityManager
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.server.connectivity.ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.any
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class CSFirewallChainTest : CSTest() {
+ @get:Rule
+ val ignoreRule = DevSdkIgnoreRule()
+
+ // Tests for setFirewallChainEnabled on FIREWALL_CHAIN_BACKGROUND
+ @Test
+ @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, false)])
+ fun setFirewallChainEnabled_backgroundChainDisabled() {
+ verifySetFirewallChainEnabledOnBackgroundDoesNothing()
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, true)])
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun setFirewallChainEnabled_backgroundChainEnabled_afterU() {
+ cm.setFirewallChainEnabled(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, true)
+ verify(bpfNetMaps).setChildChain(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, true)
+
+ clearInvocations(bpfNetMaps)
+
+ cm.setFirewallChainEnabled(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, false)
+ verify(bpfNetMaps).setChildChain(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, false)
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, true)])
+ @IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun setFirewallChainEnabled_backgroundChainEnabled_uptoU() {
+ verifySetFirewallChainEnabledOnBackgroundDoesNothing()
+ }
+
+ private fun verifySetFirewallChainEnabledOnBackgroundDoesNothing() {
+ cm.setFirewallChainEnabled(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, true)
+ verify(bpfNetMaps, never()).setChildChain(anyInt(), anyBoolean())
+
+ cm.setFirewallChainEnabled(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, false)
+ verify(bpfNetMaps, never()).setChildChain(anyInt(), anyBoolean())
+ }
+
+ // Tests for replaceFirewallChain on FIREWALL_CHAIN_BACKGROUND
+ @Test
+ @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, false)])
+ fun replaceFirewallChain_backgroundChainDisabled() {
+ verifyReplaceFirewallChainOnBackgroundDoesNothing()
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, true)])
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun replaceFirewallChain_backgroundChainEnabled_afterU() {
+ val uids = intArrayOf(53, 42, 79)
+ cm.replaceFirewallChain(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uids)
+ verify(bpfNetMaps).replaceUidChain(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uids)
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, true)])
+ @IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun replaceFirewallChain_backgroundChainEnabled_uptoU() {
+ verifyReplaceFirewallChainOnBackgroundDoesNothing()
+ }
+
+ private fun verifyReplaceFirewallChainOnBackgroundDoesNothing() {
+ val uids = intArrayOf(53, 42, 79)
+ cm.replaceFirewallChain(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uids)
+ verify(bpfNetMaps, never()).replaceUidChain(anyInt(), any(IntArray::class.java))
+ }
+
+ // Tests for setUidFirewallRule on FIREWALL_CHAIN_BACKGROUND
+ @Test
+ @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, false)])
+ fun setUidFirewallRule_backgroundChainDisabled() {
+ verifySetUidFirewallRuleOnBackgroundDoesNothing()
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, true)])
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun setUidFirewallRule_backgroundChainEnabled_afterU() {
+ val uid = 2345
+
+ cm.setUidFirewallRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid,
+ ConnectivityManager.FIREWALL_RULE_DEFAULT)
+ verify(bpfNetMaps).setUidRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid,
+ ConnectivityManager.FIREWALL_RULE_DENY)
+
+ clearInvocations(bpfNetMaps)
+
+ cm.setUidFirewallRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid,
+ ConnectivityManager.FIREWALL_RULE_DENY)
+ verify(bpfNetMaps).setUidRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid,
+ ConnectivityManager.FIREWALL_RULE_DENY)
+
+ clearInvocations(bpfNetMaps)
+
+ cm.setUidFirewallRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid,
+ ConnectivityManager.FIREWALL_RULE_ALLOW)
+ verify(bpfNetMaps).setUidRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid,
+ ConnectivityManager.FIREWALL_RULE_ALLOW)
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, true)])
+ @IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun setUidFirewallRule_backgroundChainEnabled_uptoU() {
+ verifySetUidFirewallRuleOnBackgroundDoesNothing()
+ }
+
+ private fun verifySetUidFirewallRuleOnBackgroundDoesNothing() {
+ val uid = 2345
+
+ listOf(ConnectivityManager.FIREWALL_RULE_DEFAULT, ConnectivityManager.FIREWALL_RULE_ALLOW,
+ ConnectivityManager.FIREWALL_RULE_DENY).forEach { rule ->
+ cm.setUidFirewallRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid, rule)
+ verify(bpfNetMaps, never()).setUidRule(anyInt(), anyInt(), anyInt())
+ }
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index bd26c63..3b06ad0 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -161,6 +161,7 @@
it[ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS] = true
it[ConnectivityService.ALLOW_SATALLITE_NETWORK_FALLBACK] = true
it[ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING] = true
+ it[ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN] = true
}
fun setFeatureEnabled(flag: String, enabled: Boolean) = enabledFeatures.set(flag, enabled)
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index d80dcfb..63cd574 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -699,6 +699,7 @@
new NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
.addCapability(NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
.build();
final NetworkScore score =
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index 9a81388..ba7392c 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -17,6 +17,12 @@
package android.net.thread.cts;
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_CHILD;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_ROUTER;
@@ -805,6 +811,20 @@
assertThat(isAttached(mController)).isTrue();
assertThat(networkFuture.get(NETWORK_CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNotNull();
+ NetworkCapabilities caps =
+ runAsShell(
+ ACCESS_NETWORK_STATE, () -> cm.getNetworkCapabilities(networkFuture.get()));
+ assertThat(caps).isNotNull();
+ assertThat(caps.hasTransport(NetworkCapabilities.TRANSPORT_THREAD)).isTrue();
+ assertThat(caps.getCapabilities())
+ .asList()
+ .containsAtLeast(
+ NET_CAPABILITY_LOCAL_NETWORK,
+ NET_CAPABILITY_NOT_METERED,
+ NET_CAPABILITY_NOT_RESTRICTED,
+ NET_CAPABILITY_NOT_VCN_MANAGED,
+ NET_CAPABILITY_NOT_VPN,
+ NET_CAPABILITY_TRUSTED);
}
private void grantPermissions(String... permissions) {
diff --git a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
index 491331c..5a8d21f 100644
--- a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
+++ b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
@@ -75,7 +75,6 @@
@RequiresThreadFeature
@RequiresSimulationThreadDevice
@LargeTest
-@Ignore("TODO: b/328527773 - enable the test when it's stable")
public class ServiceDiscoveryTest {
private static final String TAG = ServiceDiscoveryTest.class.getSimpleName();
private static final int NUM_FTD = 3;
@@ -103,11 +102,13 @@
private HandlerThread mHandlerThread;
private NsdManager mNsdManager;
private TapTestNetworkTracker mTestNetworkTracker;
- private List<FullThreadDevice> mFtds;
- private List<RegistrationListener> mRegistrationListeners = new ArrayList<>();
+ private final List<FullThreadDevice> mFtds = new ArrayList<>();
+ private final List<RegistrationListener> mRegistrationListeners = new ArrayList<>();
@Before
public void setUp() throws Exception {
+ mOtCtl.factoryReset();
+ mController.setEnabledAndWait(true);
mController.joinAndWait(DEFAULT_DATASET);
mNsdManager = mContext.getSystemService(NsdManager.class);
@@ -120,7 +121,6 @@
// Create the FTDs in setUp() so that the FTDs can be safely released in tearDown().
// Don't create new FTDs in test cases.
- mFtds = new ArrayList<>();
for (int i = 0; i < NUM_FTD; ++i) {
FullThreadDevice ftd = new FullThreadDevice(10 + i /* node ID */);
ftd.autoStartSrpClient();
@@ -321,6 +321,7 @@
}
@Test
+ @Ignore("TODO: b/332452386 - Enable this test case when it handles the multi-client case well")
public void discoveryProxy_multipleClientsBrowseAndResolveServiceOverMdns() throws Exception {
/*
* <pre>