Merge "RateLimitTest: increase tolerance to 30%"
diff --git a/Cronet/tests/cts/AndroidTest.xml b/Cronet/tests/cts/AndroidTest.xml
index 1f6bdb3..d2422f1 100644
--- a/Cronet/tests/cts/AndroidTest.xml
+++ b/Cronet/tests/cts/AndroidTest.xml
@@ -17,7 +17,8 @@
<configuration description="Config for CTS Cronet test cases">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="networking" />
- <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <!-- Instant apps cannot create sockets. See b/264248246 -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index 481557b..3f4da10 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -17,6 +17,18 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+// TODO: This is currently not used, but is being merged early, so Cronet can be disabled in
+// tm-mainline-prod.
+// Both cronet_java_defaults and cronet_java_prejarjar_defaults can be used to
+// specify a java_defaults target that either enables or disables Cronet. This
+// is used to disable Cronet on tm-mainline-prod.
+// Note: they must either both be enabled or disabled.
+cronet_java_defaults = "CronetJavaDefaultsEnabled"
+cronet_java_prejarjar_defaults = "CronetJavaPrejarjarDefaultsEnabled"
+// This is a placeholder comment to avoid merge conflicts
+// as cronet_defaults may have different values
+// depending on the branch
+
java_sdk_library {
name: "framework-tethering",
defaults: ["framework-module-defaults"],
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 72f83fa..44d3ffc 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -27,6 +27,7 @@
import static android.system.OsConstants.ETH_P_IP;
import static android.system.OsConstants.ETH_P_IPV6;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_MIN_MTU;
import static com.android.net.module.util.ip.ConntrackMonitor.ConntrackEvent;
import static com.android.networkstack.tethering.BpfUtils.DOWNSTREAM;
import static com.android.networkstack.tethering.BpfUtils.UPSTREAM;
@@ -82,6 +83,8 @@
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -143,6 +146,8 @@
static final int NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED = 432_000;
@VisibleForTesting
static final int NF_CONNTRACK_UDP_TIMEOUT_STREAM = 180;
+ @VisibleForTesting
+ static final int INVALID_MTU = 0;
// List of TCP port numbers which aren't offloaded because the packets require the netfilter
// conntrack helper. See also TetherController::setForwardRules in netd.
@@ -263,6 +268,10 @@
// TODO: Support multi-upstream interfaces.
private int mLastIPv4UpstreamIfindex = 0;
+ // Tracks the IPv4 upstream interface information.
+ @Nullable
+ private UpstreamInfo mIpv4UpstreamInfo = null;
+
// Runnable that used by scheduling next polling of stats.
private final Runnable mScheduledPollingStats = () -> {
updateForwardedStats();
@@ -320,6 +329,19 @@
return SdkLevel.isAtLeastS();
}
+ /**
+ * Gets the MTU of the given interface.
+ */
+ public int getNetworkInterfaceMtu(@NonNull String iface) {
+ try {
+ final NetworkInterface networkInterface = NetworkInterface.getByName(iface);
+ return networkInterface == null ? INVALID_MTU : networkInterface.getMTU();
+ } catch (SocketException e) {
+ Log.e(TAG, "Could not get MTU for interface " + iface, e);
+ return INVALID_MTU;
+ }
+ }
+
/** Get downstream4 BPF map. */
@Nullable public IBpfMap<Tether4Key, Tether4Value> getBpfDownstream4Map() {
if (!isAtLeastS()) return null;
@@ -868,6 +890,7 @@
if (!isUsingBpf()) return;
int upstreamIndex = 0;
+ int mtu = INVALID_MTU;
// This will not work on a network that is using 464xlat because hasIpv4Address will not be
// true.
@@ -877,6 +900,17 @@
final String ifaceName = ns.linkProperties.getInterfaceName();
final InterfaceParams params = mDeps.getInterfaceParams(ifaceName);
final boolean isVcn = isVcnInterface(ifaceName);
+ mtu = ns.linkProperties.getMtu();
+ if (mtu == INVALID_MTU) {
+ // Get mtu via kernel if mtu is not found in LinkProperties.
+ mtu = mDeps.getNetworkInterfaceMtu(ifaceName);
+ }
+
+ // Use default mtu if can't find any.
+ if (mtu == INVALID_MTU) mtu = NetworkStackConstants.ETHER_MTU;
+ // Clamp to minimum ipv4 mtu
+ if (mtu < IPV4_MIN_MTU) mtu = IPV4_MIN_MTU;
+
if (!isVcn && params != null && !params.hasMacAddress /* raw ip upstream only */) {
upstreamIndex = params.index;
}
@@ -905,8 +939,11 @@
// after the upstream is lost do not incorrectly add rules pointing at the upstream.
if (upstreamIndex == 0) {
mIpv4UpstreamIndices.clear();
+ mIpv4UpstreamInfo = null;
return;
}
+
+ mIpv4UpstreamInfo = new UpstreamInfo(upstreamIndex, mtu);
Collection<InetAddress> addresses = ns.linkProperties.getAddresses();
for (final InetAddress addr: addresses) {
if (isValidUpstreamIpv4Address(addr)) {
@@ -1051,6 +1088,9 @@
}
pw.decreaseIndent();
+ pw.println("IPv4 Upstream Information: "
+ + (mIpv4UpstreamInfo != null ? mIpv4UpstreamInfo : "<empty>"));
+
pw.println();
pw.println("Forwarding counters:");
pw.increaseIndent();
@@ -1258,10 +1298,10 @@
final String ageStr = (value.lastUsed == 0) ? "-"
: String.format("%dms", (now - value.lastUsed) / 1_000_000);
- return String.format("%s [%s] %d(%s) %s:%d -> %d(%s) %s:%d -> %s:%d [%s] %s",
+ return String.format("%s [%s] %d(%s) %s:%d -> %d(%s) %s:%d -> %s:%d [%s] %d %s",
l4protoToString(key.l4proto), key.dstMac, key.iif, getIfName(key.iif),
src4, key.srcPort, value.oif, getIfName(value.oif),
- public4, publicPort, dst4, value.dstPort, value.ethDstMac, ageStr);
+ public4, publicPort, dst4, value.dstPort, value.ethDstMac, value.pmtu, ageStr);
}
private void dumpIpv4ForwardingRuleMap(long now, boolean downstream,
@@ -1283,13 +1323,13 @@
try (IBpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map();
IBpfMap<Tether4Key, Tether4Value> downstreamMap = mDeps.getBpfDownstream4Map()) {
pw.println("IPv4 Upstream: proto [inDstMac] iif(iface) src -> nat -> "
- + "dst [outDstMac] age");
+ + "dst [outDstMac] pmtu age");
pw.increaseIndent();
dumpIpv4ForwardingRuleMap(now, UPSTREAM, upstreamMap, pw);
pw.decreaseIndent();
pw.println("IPv4 Downstream: proto [inDstMac] iif(iface) src -> nat -> "
- + "dst [outDstMac] age");
+ + "dst [outDstMac] pmtu age");
pw.increaseIndent();
dumpIpv4ForwardingRuleMap(now, DOWNSTREAM, downstreamMap, pw);
pw.decreaseIndent();
@@ -1540,6 +1580,28 @@
}
}
+ /** Upstream information class. */
+ private static final class UpstreamInfo {
+ // TODO: add clat interface information
+ public final int ifIndex;
+ public final int mtu;
+
+ private UpstreamInfo(final int ifIndex, final int mtu) {
+ this.ifIndex = ifIndex;
+ this.mtu = mtu;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ifIndex, mtu);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("ifIndex: %d, mtu: %d", ifIndex, mtu);
+ }
+ }
+
/**
* A BPF tethering stats provider to provide network statistics to the system.
* Note that this class' data may only be accessed on the handler thread.
@@ -1711,20 +1773,20 @@
@NonNull
private Tether4Value makeTetherUpstream4Value(@NonNull ConntrackEvent e,
- int upstreamIndex) {
- return new Tether4Value(upstreamIndex,
+ @NonNull UpstreamInfo upstreamInfo) {
+ return new Tether4Value(upstreamInfo.ifIndex,
NULL_MAC_ADDRESS /* ethDstMac (rawip) */,
NULL_MAC_ADDRESS /* ethSrcMac (rawip) */, ETH_P_IP,
- NetworkStackConstants.ETHER_MTU, toIpv4MappedAddressBytes(e.tupleReply.dstIp),
+ upstreamInfo.mtu, toIpv4MappedAddressBytes(e.tupleReply.dstIp),
toIpv4MappedAddressBytes(e.tupleReply.srcIp), e.tupleReply.dstPort,
e.tupleReply.srcPort, 0 /* lastUsed, filled by bpf prog only */);
}
@NonNull
private Tether4Value makeTetherDownstream4Value(@NonNull ConntrackEvent e,
- @NonNull ClientInfo c, int upstreamIndex) {
+ @NonNull ClientInfo c, @NonNull UpstreamInfo upstreamInfo) {
return new Tether4Value(c.downstreamIfindex,
- c.clientMac, c.downstreamMac, ETH_P_IP, NetworkStackConstants.ETHER_MTU,
+ c.clientMac, c.downstreamMac, ETH_P_IP, upstreamInfo.mtu,
toIpv4MappedAddressBytes(e.tupleOrig.dstIp),
toIpv4MappedAddressBytes(e.tupleOrig.srcIp),
e.tupleOrig.dstPort, e.tupleOrig.srcPort,
@@ -1773,9 +1835,11 @@
return;
}
- final Tether4Value upstream4Value = makeTetherUpstream4Value(e, upstreamIndex);
+ if (mIpv4UpstreamInfo == null || mIpv4UpstreamInfo.ifIndex != upstreamIndex) return;
+
+ final Tether4Value upstream4Value = makeTetherUpstream4Value(e, mIpv4UpstreamInfo);
final Tether4Value downstream4Value = makeTetherDownstream4Value(e, tetherClient,
- upstreamIndex);
+ mIpv4UpstreamInfo);
maybeAddDevMap(upstreamIndex, tetherClient.downstreamIfindex);
maybeSetLimit(upstreamIndex);
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index b3ec805..e1b7016 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -2407,6 +2407,9 @@
/** Unregister tethering event callback */
void unregisterTetheringEventCallback(ITetheringEventCallback callback) {
+ if (callback == null) {
+ throw new NullPointerException();
+ }
mHandler.post(() -> {
mTetheringEventCallbacks.unregister(callback);
});
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index ace5f15..1978e99 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -32,6 +32,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_MIN_MTU;
import static com.android.net.module.util.ip.ConntrackMonitor.ConntrackEvent;
import static com.android.net.module.util.netlink.ConntrackMessage.DYING_MASK;
import static com.android.net.module.util.netlink.ConntrackMessage.ESTABLISHED_MASK;
@@ -41,6 +42,7 @@
import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_DELETE;
import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW;
import static com.android.networkstack.tethering.BpfCoordinator.CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS;
+import static com.android.networkstack.tethering.BpfCoordinator.INVALID_MTU;
import static com.android.networkstack.tethering.BpfCoordinator.NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED;
import static com.android.networkstack.tethering.BpfCoordinator.NF_CONNTRACK_UDP_TIMEOUT_STREAM;
import static com.android.networkstack.tethering.BpfCoordinator.NON_OFFLOADED_UPSTREAM_IPV4_TCP_PORTS;
@@ -283,6 +285,11 @@
private int mDstPort = REMOTE_PORT;
private long mLastUsed = 0;
+ public Builder setPmtu(short pmtu) {
+ mPmtu = pmtu;
+ return this;
+ }
+
public Tether4Value build() {
return new Tether4Value(mOif, mEthDstMac, mEthSrcMac, mEthProto, mPmtu,
mSrc46, mDst46, mSrcPort, mDstPort, mLastUsed);
@@ -303,6 +310,11 @@
private int mDstPort = PRIVATE_PORT;
private long mLastUsed = 0;
+ public Builder setPmtu(short pmtu) {
+ mPmtu = pmtu;
+ return this;
+ }
+
public Tether4Value build() {
return new Tether4Value(mOif, mEthDstMac, mEthSrcMac, mEthProto, mPmtu,
mSrc46, mDst46, mSrcPort, mDstPort, mLastUsed);
@@ -375,6 +387,7 @@
private HashMap<IpServer, HashMap<Inet4Address, ClientInfo>> mTetherClients;
private long mElapsedRealtimeNanos = 0;
+ private int mMtu = NetworkStackConstants.ETHER_MTU;
private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
ArgumentCaptor.forClass(ArrayList.class);
private final TestLooper mTestLooper = new TestLooper();
@@ -430,6 +443,10 @@
return mElapsedRealtimeNanos;
}
+ public int getNetworkInterfaceMtu(@NonNull String iface) {
+ return mMtu;
+ }
+
@Nullable
public IBpfMap<Tether4Key, Tether4Value> getBpfDownstream4Map() {
return mBpfDownstream4Map;
@@ -1518,6 +1535,7 @@
final LinkProperties lp = new LinkProperties();
lp.setInterfaceName(upstreamInfo.interfaceParams.name);
lp.addLinkAddress(new LinkAddress(upstreamInfo.address, 32 /* prefix length */));
+ lp.setMtu(mMtu);
final NetworkCapabilities capabilities = new NetworkCapabilities()
.addTransportType(upstreamInfo.transportType);
coordinator.updateUpstreamNetworkState(new UpstreamNetworkState(lp, capabilities,
@@ -2195,4 +2213,72 @@
verifyDump(coordinator);
}
+
+ private void verifyAddTetherOffloadRule4Mtu(final int ifaceMtu, final boolean isKernelMtu,
+ final int expectedMtu) throws Exception {
+ // BpfCoordinator#updateUpstreamNetworkState geta mtu from LinkProperties. If not found,
+ // try to get from kernel.
+ if (isKernelMtu) {
+ // LinkProperties mtu is invalid and kernel mtu is valid.
+ mMtu = INVALID_MTU;
+ doReturn(ifaceMtu).when(mDeps).getNetworkInterfaceMtu(any());
+ } else {
+ // LinkProperties mtu is valid and kernel mtu is invalid.
+ mMtu = ifaceMtu;
+ doReturn(INVALID_MTU).when(mDeps).getNetworkInterfaceMtu(any());
+ }
+
+ final BpfCoordinator coordinator = makeBpfCoordinator();
+ initBpfCoordinatorForRule4(coordinator);
+
+ final Tether4Key expectedUpstream4KeyTcp = new TestUpstream4Key.Builder()
+ .setProto(IPPROTO_TCP)
+ .build();
+ final Tether4Key expectedDownstream4KeyTcp = new TestDownstream4Key.Builder()
+ .setProto(IPPROTO_TCP)
+ .build();
+ final Tether4Value expectedUpstream4ValueTcp = new TestUpstream4Value.Builder()
+ .setPmtu((short) expectedMtu)
+ .build();
+ final Tether4Value expectedDownstream4ValueTcp = new TestDownstream4Value.Builder()
+ .setPmtu((short) expectedMtu)
+ .build();
+
+ mConsumer.accept(new TestConntrackEvent.Builder()
+ .setMsgType(IPCTNL_MSG_CT_NEW)
+ .setProto(IPPROTO_TCP)
+ .build());
+ verify(mBpfUpstream4Map)
+ .insertEntry(eq(expectedUpstream4KeyTcp), eq(expectedUpstream4ValueTcp));
+ verify(mBpfDownstream4Map)
+ .insertEntry(eq(expectedDownstream4KeyTcp), eq(expectedDownstream4ValueTcp));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testAddTetherOffloadRule4LowMtuFromLinkProperties() throws Exception {
+ verifyAddTetherOffloadRule4Mtu(
+ IPV4_MIN_MTU, false /* isKernelMtu */, IPV4_MIN_MTU /* expectedMtu */);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testAddTetherOffloadRule4LowMtuFromKernel() throws Exception {
+ verifyAddTetherOffloadRule4Mtu(
+ IPV4_MIN_MTU, true /* isKernelMtu */, IPV4_MIN_MTU /* expectedMtu */);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testAddTetherOffloadRule4LessThanIpv4MinMtu() throws Exception {
+ verifyAddTetherOffloadRule4Mtu(
+ IPV4_MIN_MTU - 1, false /* isKernelMtu */, IPV4_MIN_MTU /* expectedMtu */);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testAddTetherOffloadRule4InvalidMtu() throws Exception {
+ verifyAddTetherOffloadRule4Mtu(INVALID_MTU, false /* isKernelMtu */,
+ NetworkStackConstants.ETHER_MTU /* expectedMtu */);
+ }
}
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index 563ca5e..7350209 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -52,9 +52,17 @@
__be32 identification;
};
+// constants for passing in to 'bool is_ethernet'
+static const bool RAWIP = false;
+static const bool ETHER = true;
+
+#define KVER_4_14 KVER(4, 14, 0)
+
DEFINE_BPF_MAP_GRW(clat_ingress6_map, HASH, ClatIngress6Key, ClatIngress6Value, 16, AID_SYSTEM)
-static inline __always_inline int nat64(struct __sk_buff* skb, bool is_ethernet) {
+static inline __always_inline int nat64(struct __sk_buff* skb,
+ const bool is_ethernet,
+ const unsigned kver) {
// Require ethernet dst mac address to be our unicast address.
if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE;
@@ -103,6 +111,31 @@
__u8 proto = ip6->nexthdr;
__be16 ip_id = 0;
__be16 frag_off = htons(IP_DF);
+ __u16 tot_len = ntohs(ip6->payload_len) + sizeof(struct iphdr); // cannot overflow, see above
+
+ if (proto == IPPROTO_FRAGMENT) {
+ // Fragment handling requires bpf_skb_adjust_room which is 4.14+
+ if (kver < KVER_4_14) return TC_ACT_PIPE;
+
+ // Must have (ethernet and) ipv6 header and ipv6 fragment extension header
+ if (data + l2_header_size + sizeof(*ip6) + sizeof(struct frag_hdr) > data_end)
+ return TC_ACT_PIPE;
+ const struct frag_hdr *frag = (const struct frag_hdr *)(ip6 + 1);
+ proto = frag->nexthdr;
+ // RFC6145: use bottom 16-bits of network endian 32-bit IPv6 ID field for 16-bit IPv4 field.
+ // this is equivalent to: ip_id = htons(ntohl(frag->identification));
+ ip_id = frag->identification >> 16;
+ // Conversion of 16-bit IPv6 frag offset to 16-bit IPv4 frag offset field.
+ // IPv6 is '13 bits of offset in multiples of 8' + 2 zero bits + more fragment bit
+ // IPv4 is zero bit + don't frag bit + more frag bit + '13 bits of offset in multiples of 8'
+ frag_off = ntohs(frag->frag_off);
+ frag_off = ((frag_off & 1) << 13) | (frag_off >> 3);
+ frag_off = htons(frag_off);
+ // Note that by construction tot_len is guaranteed to not underflow here
+ tot_len -= sizeof(struct frag_hdr);
+ // This is a badly formed IPv6 packet with less payload than the size of an IPv6 Frag EH
+ if (tot_len < sizeof(struct iphdr)) return TC_ACT_PIPE;
+ }
switch (proto) {
case IPPROTO_TCP: // For TCP & UDP the checksum neutrality of the chosen IPv6
@@ -129,7 +162,7 @@
.version = 4, // u4
.ihl = sizeof(struct iphdr) / sizeof(__u32), // u4
.tos = (ip6->priority << 4) + (ip6->flow_lbl[0] >> 4), // u8
- .tot_len = htons(ntohs(ip6->payload_len) + sizeof(struct iphdr)), // be16
+ .tot_len = htons(tot_len), // be16
.id = ip_id, // be16
.frag_off = frag_off, // be16
.ttl = ip6->hop_limit, // u8
@@ -186,6 +219,26 @@
// return -ENOTSUPP;
bpf_csum_update(skb, sum6);
+ // Technically 'kver < KVER_4_14' already implies 'frag_off == htons(IP_DF)' due to logic above,
+ // thus the initial 'kver >= KVER_4_14' check here is entirely superfluous.
+ //
+ // However, we *need* the compiler (when compiling the program for 4.9) to entirely
+ // optimize out the call to bpf_skb_adjust_room() bpf helper: it's not enough for it to emit
+ // an unreachable call to it, it must *not* emit it at all (otherwise the 4.9 kernel's
+ // bpf verifier will refuse to load a program with an unknown bpf helper call)
+ //
+ // This is easiest to achieve by being very explicit in the if clause,
+ // better safe than sorry...
+ //
+ // Note: we currently have no TreeHugger coverage for 4.9-T devices (there are no such
+ // Pixel or cuttlefish devices), so likely you won't notice for months if this breaks...
+ if (kver >= KVER_4_14 && frag_off != htons(IP_DF)) {
+ // If we're converting an IPv6 Fragment, we need to trim off 8 more bytes
+ // We're beyond recovery on error here... but hard to imagine how this could fail.
+ if (bpf_skb_adjust_room(skb, -(__s32)sizeof(struct frag_hdr), BPF_ADJ_ROOM_NET, /*flags*/0))
+ return TC_ACT_SHOT;
+ }
+
// bpf_skb_change_proto() invalidates all pointers - reload them.
data = (void*)(long)skb->data;
data_end = (void*)(long)skb->data_end;
@@ -214,14 +267,24 @@
return TC_ACT_PIPE;
}
-DEFINE_BPF_PROG("schedcls/ingress6/clat_ether", AID_ROOT, AID_SYSTEM, sched_cls_ingress6_clat_ether)
+DEFINE_BPF_PROG_KVER("schedcls/ingress6/clat_ether$4_14", AID_ROOT, AID_SYSTEM, sched_cls_ingress6_clat_ether_4_14, KVER_4_14)
(struct __sk_buff* skb) {
- return nat64(skb, true);
+ return nat64(skb, ETHER, KVER_4_14);
}
-DEFINE_BPF_PROG("schedcls/ingress6/clat_rawip", AID_ROOT, AID_SYSTEM, sched_cls_ingress6_clat_rawip)
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/ingress6/clat_ether$4_9", AID_ROOT, AID_SYSTEM, sched_cls_ingress6_clat_ether_4_9, KVER_NONE, KVER_4_14)
(struct __sk_buff* skb) {
- return nat64(skb, false);
+ return nat64(skb, ETHER, KVER_NONE);
+}
+
+DEFINE_BPF_PROG_KVER("schedcls/ingress6/clat_rawip$4_14", AID_ROOT, AID_SYSTEM, sched_cls_ingress6_clat_rawip_4_14, KVER_4_14)
+(struct __sk_buff* skb) {
+ return nat64(skb, RAWIP, KVER_4_14);
+}
+
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/ingress6/clat_rawip$4_9", AID_ROOT, AID_SYSTEM, sched_cls_ingress6_clat_rawip_4_9, KVER_NONE, KVER_4_14)
+(struct __sk_buff* skb) {
+ return nat64(skb, RAWIP, KVER_NONE);
}
DEFINE_BPF_MAP_GRW(clat_egress4_map, HASH, ClatEgress4Key, ClatEgress4Value, 16, AID_SYSTEM)
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index b0246f6..43920d0 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -109,8 +109,9 @@
// (this is because these are currently attached by the mainline provided libnetd_updatable .so
// which is loaded into netd and thus runs as netd uid/gid/selinux context)
#define DEFINE_NETD_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, minKV, maxKV) \
- DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, \
- minKV, maxKV, false, "fs_bpf_netd_readonly", "")
+ DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, \
+ minKV, maxKV, BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, false, \
+ "fs_bpf_netd_readonly", "", false, false, false)
#define DEFINE_NETD_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv) \
DEFINE_NETD_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, KVER_INF)
@@ -120,8 +121,9 @@
// programs that only need to be usable by the system server
#define DEFINE_SYS_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
- DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, \
- KVER_NONE, KVER_INF, false, "fs_bpf_net_shared", "")
+ DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, KVER_NONE, KVER_INF, \
+ BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, false, "fs_bpf_net_shared", \
+ "", false, false, false)
static __always_inline int is_system_uid(uint32_t uid) {
// MIN_SYSTEM_UID is AID_ROOT == 0, so uint32_t is *always* >= 0
diff --git a/framework-t/src/android/net/IpSecManager.java b/framework-t/src/android/net/IpSecManager.java
index 1c83e09..ff021d6 100644
--- a/framework-t/src/android/net/IpSecManager.java
+++ b/framework-t/src/android/net/IpSecManager.java
@@ -610,7 +610,7 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
- mCloseGuard.open("constructor");
+ mCloseGuard.open("close");
}
/** Get the encapsulation socket's file descriptor. */
@@ -890,7 +890,7 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
- mCloseGuard.open("constructor");
+ mCloseGuard.open("close");
}
/**
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index 7669e0e..f623b05 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -63,6 +63,7 @@
field public static final int FIREWALL_RULE_DENY = 2; // 0x2
field public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; // 0x0
field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1
+ field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING = 3; // 0x3
field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK = 2; // 0x2
}
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 60bc68c..40defd4 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -1232,16 +1232,19 @@
}
/**
- * Preference for {@link ProfileNetworkPreference#setPreference(int)}.
+ * Preference for {@link ProfileNetworkPreference.Builder#setPreference(int)}.
* See {@link #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)}
- * Specify that the traffic for this user should by follow the default rules.
+ * Specify that the traffic for this user should by follow the default rules:
+ * applications in the profile designated by the UserHandle behave like any
+ * other application and use the system default network as their default
+ * network. Compare other PROFILE_NETWORK_PREFERENCE_* settings.
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0;
/**
- * Preference for {@link ProfileNetworkPreference#setPreference(int)}.
+ * Preference for {@link ProfileNetworkPreference.Builder#setPreference(int)}.
* See {@link #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)}
* Specify that the traffic for this user should by default go on a network with
* {@link NetworkCapabilities#NET_CAPABILITY_ENTERPRISE}, and on the system default network
@@ -1252,16 +1255,38 @@
public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1;
/**
- * Preference for {@link ProfileNetworkPreference#setPreference(int)}.
+ * Preference for {@link ProfileNetworkPreference.Builder#setPreference(int)}.
* See {@link #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)}
* Specify that the traffic for this user should by default go on a network with
* {@link NetworkCapabilities#NET_CAPABILITY_ENTERPRISE} and if no such network is available
- * should not go on the system default network
+ * should not have a default network at all (that is, network accesses that
+ * do not specify a network explicitly terminate with an error), even if there
+ * is a system default network available to apps outside this preference.
+ * The apps can still use a non-enterprise network if they request it explicitly
+ * provided that specific network doesn't require any specific permission they
+ * do not hold.
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK = 2;
+ /**
+ * Preference for {@link ProfileNetworkPreference.Builder#setPreference(int)}.
+ * See {@link #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)}
+ * Specify that the traffic for this user should by default go on a network with
+ * {@link NetworkCapabilities#NET_CAPABILITY_ENTERPRISE}.
+ * If there is no such network, the apps will have no default
+ * network at all, even if there are available non-enterprise networks on the
+ * device (that is, network accesses that do not specify a network explicitly
+ * terminate with an error). Additionally, the designated apps should be
+ * blocked from using any non-enterprise network even if they specify it
+ * explicitly, unless they hold specific privilege overriding this (see
+ * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS}).
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING = 3;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index cf53002..3a17bdd 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -69,6 +69,7 @@
import android.annotation.Nullable;
import android.annotation.TargetApi;
import android.app.AlarmManager;
+import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.app.usage.NetworkStatsManager;
import android.content.ApexEnvironment;
@@ -114,6 +115,7 @@
import android.net.netstats.provider.NetworkStatsProvider;
import android.os.Binder;
import android.os.Build;
+import android.os.Bundle;
import android.os.DropBoxManager;
import android.os.Environment;
import android.os.Handler;
@@ -149,6 +151,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FileRotator;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
import com.android.net.module.util.BestClock;
import com.android.net.module.util.BinderUtils;
@@ -166,6 +169,9 @@
import com.android.net.module.util.Struct.U8;
import com.android.net.module.util.bpf.CookieTagMapKey;
import com.android.net.module.util.bpf.CookieTagMapValue;
+import com.android.networkstack.apishim.BroadcastOptionsShimImpl;
+import com.android.networkstack.apishim.ConstantsShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.server.BpfNetMaps;
import java.io.File;
@@ -526,8 +532,22 @@
case MSG_BROADCAST_NETWORK_STATS_UPDATED: {
final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED);
updatedIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ Bundle opts = null;
+ if (SdkLevel.isAtLeastU()) {
+ try {
+ // This allows us to discard older broadcasts still waiting to
+ // be delivered.
+ opts = BroadcastOptionsShimImpl.newInstance(
+ BroadcastOptions.makeBasic())
+ .setDeliveryGroupPolicy(
+ ConstantsShim.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .toBundle();
+ } catch (UnsupportedApiLevelException e) {
+ Log.wtf(TAG, "Using unsupported API" + e);
+ }
+ }
mContext.sendBroadcastAsUser(updatedIntent, UserHandle.ALL,
- READ_NETWORK_USAGE_HISTORY);
+ READ_NETWORK_USAGE_HISTORY, opts);
break;
}
}
diff --git a/service/Android.bp b/service/Android.bp
index 224fa19..8fa6436 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -206,6 +206,7 @@
libs: [
"framework-annotations-lib",
"framework-connectivity-pre-jarjar",
+ "framework-connectivity-t-pre-jarjar",
"framework-tethering",
"framework-wifi",
"service-connectivity-pre-jarjar",
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service/mdns/com/android/server/connectivity/mdns/MdnsAdvertiser.java
index dee78fd..185fac1 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -16,14 +16,401 @@
package com.android.server.connectivity.mdns;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.LinkAddress;
+import android.net.Network;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import android.os.Looper;
+import android.util.ArrayMap;
import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
/**
* MdnsAdvertiser manages advertising services per {@link com.android.server.NsdService} requests.
*
- * TODO: implement
+ * All methods except the constructor must be called on the looper thread.
*/
public class MdnsAdvertiser {
private static final String TAG = MdnsAdvertiser.class.getSimpleName();
- public static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+ static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final Looper mLooper;
+ private final AdvertiserCallback mCb;
+
+ // Max-sized buffers to be used as temporary buffer to read/build packets. May be used by
+ // multiple components, but only for self-contained operations in the looper thread, so not
+ // concurrently.
+ // TODO: set according to MTU. 1300 should fit for ethernet MTU 1500 with some overhead.
+ private final byte[] mPacketCreationBuffer = new byte[1300];
+
+ private final MdnsSocketProvider mSocketProvider;
+ private final ArrayMap<Network, InterfaceAdvertiserRequest> mAdvertiserRequests =
+ new ArrayMap<>();
+ private final ArrayMap<MdnsInterfaceSocket, MdnsInterfaceAdvertiser> mAllAdvertisers =
+ new ArrayMap<>();
+ private final SparseArray<Registration> mRegistrations = new SparseArray<>();
+ private final Dependencies mDeps;
+
+ /**
+ * Dependencies for {@link MdnsAdvertiser}, useful for testing.
+ */
+ @VisibleForTesting
+ public static class Dependencies {
+ /**
+ * @see MdnsInterfaceAdvertiser
+ */
+ public MdnsInterfaceAdvertiser makeAdvertiser(@NonNull MdnsInterfaceSocket socket,
+ @NonNull List<LinkAddress> initialAddresses,
+ @NonNull Looper looper, @NonNull byte[] packetCreationBuffer,
+ @NonNull MdnsInterfaceAdvertiser.Callback cb) {
+ // Note NetworkInterface is final and not mockable
+ final String logTag = socket.getInterface().getName();
+ return new MdnsInterfaceAdvertiser(logTag, socket, initialAddresses, looper,
+ packetCreationBuffer, cb);
+ }
+ }
+
+ private final MdnsInterfaceAdvertiser.Callback mInterfaceAdvertiserCb =
+ new MdnsInterfaceAdvertiser.Callback() {
+ @Override
+ public void onRegisterServiceSucceeded(
+ @NonNull MdnsInterfaceAdvertiser advertiser, int serviceId) {
+ // Wait for all current interfaces to be done probing before notifying of success.
+ if (anyAdvertiser(a -> a.isProbing(serviceId))) return;
+ // The service may still be unregistered/renamed if a conflict is found on a later added
+ // interface, or if a conflicting announcement/reply is detected (RFC6762 9.)
+
+ final Registration registration = mRegistrations.get(serviceId);
+ if (registration == null) {
+ Log.wtf(TAG, "Register succeeded for unknown registration");
+ return;
+ }
+ if (!registration.mNotifiedRegistrationSuccess) {
+ mCb.onRegisterServiceSucceeded(serviceId, registration.getServiceInfo());
+ registration.mNotifiedRegistrationSuccess = true;
+ }
+ }
+
+ @Override
+ public void onServiceConflict(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId) {
+ // TODO: handle conflicts found after registration (during or after probing)
+ }
+
+ @Override
+ public void onDestroyed(@NonNull MdnsInterfaceSocket socket) {
+ for (int i = mAdvertiserRequests.size() - 1; i >= 0; i--) {
+ if (mAdvertiserRequests.valueAt(i).onAdvertiserDestroyed(socket)) {
+ mAdvertiserRequests.removeAt(i);
+ }
+ }
+ mAllAdvertisers.remove(socket);
+ }
+ };
+
+ /**
+ * A request for a {@link MdnsInterfaceAdvertiser}.
+ *
+ * This class tracks services to be advertised on all sockets provided via a registered
+ * {@link MdnsSocketProvider.SocketCallback}.
+ */
+ private class InterfaceAdvertiserRequest implements MdnsSocketProvider.SocketCallback {
+ /** Registrations to add to newer MdnsInterfaceAdvertisers when sockets are created. */
+ @NonNull
+ private final SparseArray<Registration> mPendingRegistrations = new SparseArray<>();
+ @NonNull
+ private final ArrayMap<MdnsInterfaceSocket, MdnsInterfaceAdvertiser> mAdvertisers =
+ new ArrayMap<>();
+
+ InterfaceAdvertiserRequest(@Nullable Network requestedNetwork) {
+ mSocketProvider.requestSocket(requestedNetwork, this);
+ }
+
+ /**
+ * Called when an advertiser was destroyed, after all services were unregistered and it sent
+ * exit announcements, or the interface is gone.
+ *
+ * @return true if this {@link InterfaceAdvertiserRequest} should now be deleted.
+ */
+ boolean onAdvertiserDestroyed(@NonNull MdnsInterfaceSocket socket) {
+ mAdvertisers.remove(socket);
+ if (mAdvertisers.size() == 0 && mPendingRegistrations.size() == 0) {
+ // No advertiser is using sockets from this request anymore (in particular for exit
+ // announcements), and there is no registration so newer sockets will not be
+ // necessary, so the request can be unregistered.
+ mSocketProvider.unrequestSocket(this);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get the ID of a conflicting service, or -1 if none.
+ */
+ int getConflictingService(@NonNull NsdServiceInfo info) {
+ for (int i = 0; i < mPendingRegistrations.size(); i++) {
+ final NsdServiceInfo other = mPendingRegistrations.valueAt(i).getServiceInfo();
+ if (info.getServiceName().equals(other.getServiceName())
+ && info.getServiceType().equals(other.getServiceType())) {
+ return mPendingRegistrations.keyAt(i);
+ }
+ }
+ return -1;
+ }
+
+ void addService(int id, Registration registration)
+ throws NameConflictException {
+ final int conflicting = getConflictingService(registration.getServiceInfo());
+ if (conflicting >= 0) {
+ throw new NameConflictException(conflicting);
+ }
+
+ mPendingRegistrations.put(id, registration);
+ for (int i = 0; i < mAdvertisers.size(); i++) {
+ mAdvertisers.valueAt(i).addService(id, registration.getServiceInfo());
+ }
+ }
+
+ void removeService(int id) {
+ mPendingRegistrations.remove(id);
+ for (int i = 0; i < mAdvertisers.size(); i++) {
+ mAdvertisers.valueAt(i).removeService(id);
+ }
+ }
+
+ @Override
+ public void onSocketCreated(@NonNull Network network,
+ @NonNull MdnsInterfaceSocket socket,
+ @NonNull List<LinkAddress> addresses) {
+ MdnsInterfaceAdvertiser advertiser = mAllAdvertisers.get(socket);
+ if (advertiser == null) {
+ advertiser = mDeps.makeAdvertiser(socket, addresses, mLooper, mPacketCreationBuffer,
+ mInterfaceAdvertiserCb);
+ mAllAdvertisers.put(socket, advertiser);
+ advertiser.start();
+ }
+ mAdvertisers.put(socket, advertiser);
+ for (int i = 0; i < mPendingRegistrations.size(); i++) {
+ try {
+ advertiser.addService(mPendingRegistrations.keyAt(i),
+ mPendingRegistrations.valueAt(i).getServiceInfo());
+ } catch (NameConflictException e) {
+ Log.wtf(TAG, "Name conflict adding services that should have unique names", e);
+ }
+ }
+ }
+
+ @Override
+ public void onInterfaceDestroyed(@NonNull Network network,
+ @NonNull MdnsInterfaceSocket socket) {
+ final MdnsInterfaceAdvertiser advertiser = mAdvertisers.get(socket);
+ if (advertiser != null) advertiser.destroyNow();
+ }
+
+ @Override
+ public void onAddressesChanged(@NonNull Network network,
+ @NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> addresses) {
+ final MdnsInterfaceAdvertiser advertiser = mAdvertisers.get(socket);
+ if (advertiser != null) advertiser.updateAddresses(addresses);
+ }
+ }
+
+ private static class Registration {
+ @NonNull
+ final String mOriginalName;
+ boolean mNotifiedRegistrationSuccess;
+ private int mConflictCount;
+ @NonNull
+ private NsdServiceInfo mServiceInfo;
+
+ private Registration(@NonNull NsdServiceInfo serviceInfo) {
+ this.mOriginalName = serviceInfo.getServiceName();
+ this.mServiceInfo = serviceInfo;
+ }
+
+ /**
+ * Update the registration to use a different service name, after a conflict was found.
+ *
+ * If a name conflict was found during probing or because different advertising requests
+ * used the same name, the registration is attempted again with a new name (here using
+ * a number suffix, (1), (2) etc). Registration success is notified once probing succeeds
+ * with a new name. This matches legacy behavior based on mdnsresponder, and appendix D of
+ * RFC6763.
+ * @return The new service info with the updated name.
+ */
+ @NonNull
+ private NsdServiceInfo updateForConflict() {
+ mConflictCount++;
+ // In case of conflict choose a different service name. After the first conflict use
+ // "Name (2)", then "Name (3)" etc.
+ // TODO: use a hidden method in NsdServiceInfo once MdnsAdvertiser is moved to service-t
+ final NsdServiceInfo newInfo = new NsdServiceInfo();
+ newInfo.setServiceName(mOriginalName + " (" + (mConflictCount + 1) + ")");
+ newInfo.setServiceType(mServiceInfo.getServiceType());
+ for (Map.Entry<String, byte[]> attr : mServiceInfo.getAttributes().entrySet()) {
+ newInfo.setAttribute(attr.getKey(), attr.getValue());
+ }
+ newInfo.setHost(mServiceInfo.getHost());
+ newInfo.setPort(mServiceInfo.getPort());
+ newInfo.setNetwork(mServiceInfo.getNetwork());
+ // interfaceIndex is not set when registering
+
+ mServiceInfo = newInfo;
+ return mServiceInfo;
+ }
+
+ @NonNull
+ public NsdServiceInfo getServiceInfo() {
+ return mServiceInfo;
+ }
+ }
+
+ /**
+ * Callbacks for advertising services.
+ *
+ * Every method is called on the MdnsAdvertiser looper thread.
+ */
+ public interface AdvertiserCallback {
+ /**
+ * Called when a service was successfully registered, after probing.
+ *
+ * @param serviceId ID of the service provided when registering.
+ * @param registeredInfo Registered info, which may be different from the requested info,
+ * after probing and possibly choosing alternative service names.
+ */
+ void onRegisterServiceSucceeded(int serviceId, NsdServiceInfo registeredInfo);
+
+ /**
+ * Called when service registration failed.
+ *
+ * @param serviceId ID of the service provided when registering.
+ * @param errorCode One of {@code NsdManager.FAILURE_*}
+ */
+ void onRegisterServiceFailed(int serviceId, int errorCode);
+
+ // Unregistration is notified immediately as success in NsdService so no callback is needed
+ // here.
+ }
+
+ public MdnsAdvertiser(@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
+ @NonNull AdvertiserCallback cb) {
+ this(looper, socketProvider, cb, new Dependencies());
+ }
+
+ @VisibleForTesting
+ MdnsAdvertiser(@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
+ @NonNull AdvertiserCallback cb, @NonNull Dependencies deps) {
+ mLooper = looper;
+ mCb = cb;
+ mSocketProvider = socketProvider;
+ mDeps = deps;
+ }
+
+ private void checkThread() {
+ if (Thread.currentThread() != mLooper.getThread()) {
+ throw new IllegalStateException("This must be called on the looper thread");
+ }
+ }
+
+ /**
+ * Add a service to advertise.
+ * @param id A unique ID for the service.
+ * @param service The service info to advertise.
+ */
+ public void addService(int id, NsdServiceInfo service) {
+ checkThread();
+ if (mRegistrations.get(id) != null) {
+ Log.e(TAG, "Adding duplicate registration for " + service);
+ // TODO (b/264986328): add a more specific error code
+ mCb.onRegisterServiceFailed(id, NsdManager.FAILURE_INTERNAL_ERROR);
+ return;
+ }
+
+ if (DBG) {
+ Log.i(TAG, "Adding service " + service + " with ID " + id);
+ }
+
+ try {
+ final Registration registration = new Registration(service);
+ while (!tryAddRegistration(id, registration)) {
+ registration.updateForConflict();
+ }
+
+ mRegistrations.put(id, registration);
+ } catch (IOException e) {
+ Log.e(TAG, "Error adding service " + service, e);
+ removeService(id);
+ // TODO (b/264986328): add a more specific error code
+ mCb.onRegisterServiceFailed(id, NsdManager.FAILURE_INTERNAL_ERROR);
+ }
+ }
+
+ private boolean tryAddRegistration(int id, @NonNull Registration registration)
+ throws IOException {
+ final NsdServiceInfo serviceInfo = registration.getServiceInfo();
+ final Network network = serviceInfo.getNetwork();
+ try {
+ InterfaceAdvertiserRequest advertiser = mAdvertiserRequests.get(network);
+ if (advertiser == null) {
+ advertiser = new InterfaceAdvertiserRequest(network);
+ mAdvertiserRequests.put(network, advertiser);
+ }
+ advertiser.addService(id, registration);
+ } catch (NameConflictException e) {
+ if (DBG) {
+ Log.i(TAG, "Service name conflicts: " + serviceInfo.getServiceName());
+ }
+ removeService(id);
+ return false;
+ }
+
+ // When adding a service to a specific network, check that it does not conflict with other
+ // registrations advertising on all networks
+ final InterfaceAdvertiserRequest allNetworksAdvertiser = mAdvertiserRequests.get(null);
+ if (network != null && allNetworksAdvertiser != null
+ && allNetworksAdvertiser.getConflictingService(serviceInfo) >= 0) {
+ if (DBG) {
+ Log.i(TAG, "Service conflicts with advertisement on all networks: "
+ + serviceInfo.getServiceName());
+ }
+ removeService(id);
+ return false;
+ }
+
+ mRegistrations.put(id, registration);
+ return true;
+ }
+
+ /**
+ * Remove a previously added service.
+ * @param id ID used when registering.
+ */
+ public void removeService(int id) {
+ checkThread();
+ if (DBG) {
+ Log.i(TAG, "Removing service with ID " + id);
+ }
+ for (int i = mAdvertiserRequests.size() - 1; i >= 0; i--) {
+ final InterfaceAdvertiserRequest advertiser = mAdvertiserRequests.valueAt(i);
+ advertiser.removeService(id);
+ }
+ mRegistrations.remove(id);
+ }
+
+ private boolean anyAdvertiser(@NonNull Predicate<MdnsInterfaceAdvertiser> predicate) {
+ for (int i = 0; i < mAllAdvertisers.size(); i++) {
+ if (predicate.test(mAllAdvertisers.valueAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java b/service/mdns/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
new file mode 100644
index 0000000..644bdad
--- /dev/null
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2022 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.connectivity.mdns;
+
+import android.annotation.NonNull;
+import android.net.LinkAddress;
+import android.net.nsd.NsdServiceInfo;
+import android.os.Looper;
+
+import java.util.List;
+
+/**
+ * A class that handles advertising services on a {@link MdnsInterfaceSocket} tied to an interface.
+ */
+public class MdnsInterfaceAdvertiser {
+ private static final boolean DBG = MdnsAdvertiser.DBG;
+ @NonNull
+ private final String mTag;
+ @NonNull
+ private final ProbingCallback mProbingCallback = new ProbingCallback();
+ @NonNull
+ private final AnnouncingCallback mAnnouncingCallback = new AnnouncingCallback();
+ @NonNull
+ private final Callback mCb;
+ @NonNull
+ private final MdnsInterfaceSocket mSocket;
+ @NonNull
+ private final MdnsAnnouncer mAnnouncer;
+ @NonNull
+ private final MdnsProber mProber;
+ @NonNull
+ private final MdnsReplySender mReplySender;
+
+ /**
+ * Callbacks called by {@link MdnsInterfaceAdvertiser} to report status updates.
+ */
+ interface Callback {
+ /**
+ * Called by the advertiser after it successfully registered a service, after probing.
+ */
+ void onRegisterServiceSucceeded(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId);
+
+ /**
+ * Called by the advertiser when a conflict was found, during or after probing.
+ *
+ * If a conflict is found during probing, the {@link #renameServiceForConflict} must be
+ * called to restart probing and attempt registration with a different name.
+ */
+ void onServiceConflict(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId);
+
+ /**
+ * Called by the advertiser when it destroyed itself.
+ *
+ * This can happen after a call to {@link #destroyNow()}, or after all services were
+ * unregistered and the advertiser finished sending exit announcements.
+ */
+ void onDestroyed(@NonNull MdnsInterfaceSocket socket);
+ }
+
+ /**
+ * Callbacks from {@link MdnsProber}.
+ */
+ private class ProbingCallback implements
+ MdnsPacketRepeater.PacketRepeaterCallback<MdnsProber.ProbingInfo> {
+ @Override
+ public void onFinished(MdnsProber.ProbingInfo info) {
+ // TODO: probing finished, start announcements
+ }
+ }
+
+ /**
+ * Callbacks from {@link MdnsAnnouncer}.
+ */
+ private class AnnouncingCallback
+ implements MdnsPacketRepeater.PacketRepeaterCallback<MdnsAnnouncer.AnnouncementInfo> {
+ // TODO: implement
+ }
+
+ public MdnsInterfaceAdvertiser(@NonNull String logTag,
+ @NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> initialAddresses,
+ @NonNull Looper looper, @NonNull byte[] packetCreationBuffer, @NonNull Callback cb) {
+ mTag = MdnsInterfaceAdvertiser.class.getSimpleName() + "/" + logTag;
+ mSocket = socket;
+ mCb = cb;
+ mReplySender = new MdnsReplySender(looper, socket, packetCreationBuffer);
+ mAnnouncer = new MdnsAnnouncer(logTag, looper, mReplySender,
+ mAnnouncingCallback);
+ mProber = new MdnsProber(logTag, looper, mReplySender, mProbingCallback);
+ }
+
+ /**
+ * Start the advertiser.
+ *
+ * The advertiser will stop itself when all services are removed and exit announcements sent,
+ * notifying via {@link Callback#onDestroyed}. This can also be triggered manually via
+ * {@link #destroyNow()}.
+ */
+ public void start() {
+ // TODO: implement
+ }
+
+ /**
+ * Start advertising a service.
+ *
+ * @throws NameConflictException There is already a service being advertised with that name.
+ */
+ public void addService(int id, NsdServiceInfo service) throws NameConflictException {
+ // TODO: implement
+ }
+
+ /**
+ * Stop advertising a service.
+ *
+ * This will trigger exit announcements for the service.
+ */
+ public void removeService(int id) {
+ // TODO: implement
+ }
+
+ /**
+ * Update interface addresses used to advertise.
+ *
+ * This causes new address records to be announced.
+ */
+ public void updateAddresses(@NonNull List<LinkAddress> newAddresses) {
+ // TODO: implement
+ }
+
+ /**
+ * Destroy the advertiser immediately, not sending any exit announcement.
+ *
+ * <p>Useful when the underlying network went away. This will trigger an onDestroyed callback.
+ */
+ public void destroyNow() {
+ // TODO: implement
+ }
+
+ /**
+ * Reset a service to the probing state due to a conflict found on the network.
+ */
+ public void restartProbingForConflict(int serviceId) {
+ // TODO: implement
+ }
+
+ /**
+ * Rename a service following a conflict found on the network, and restart probing.
+ */
+ public void renameServiceForConflict(int serviceId, NsdServiceInfo newInfo) {
+ // TODO: implement
+ }
+
+ /**
+ * Indicates whether probing is in progress for the given service on this interface.
+ *
+ * Also returns false if the specified service is not registered.
+ */
+ public boolean isProbing(int serviceId) {
+ // TODO: implement
+ return true;
+ }
+}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java b/service/mdns/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
index 6090415..67c893d 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
@@ -154,13 +154,12 @@
}
/**
- * Returns the index of the network interface that this socket is bound to. If the interface
- * cannot be determined, returns -1.
+ * Returns the network interface that this socket is bound to.
*
* <p>This method could be used on any thread.
*/
- public int getInterfaceIndex() {
- return mNetworkInterface.getIndex();
+ public NetworkInterface getInterface() {
+ return mNetworkInterface;
}
/*** Returns whether this socket has joined IPv4 group */
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsReplySender.java b/service/mdns/com/android/server/connectivity/mdns/MdnsReplySender.java
index 1fdbc5c..adf6f4d 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsReplySender.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsReplySender.java
@@ -32,14 +32,14 @@
*/
public class MdnsReplySender {
@NonNull
- private final MulticastSocket mSocket;
+ private final MdnsInterfaceSocket mSocket;
@NonNull
private final Looper mLooper;
@NonNull
private final byte[] mPacketCreationBuffer;
public MdnsReplySender(@NonNull Looper looper,
- @NonNull MulticastSocket socket, @NonNull byte[] packetCreationBuffer) {
+ @NonNull MdnsInterfaceSocket socket, @NonNull byte[] packetCreationBuffer) {
mLooper = looper;
mSocket = socket;
mPacketCreationBuffer = packetCreationBuffer;
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsSocketProvider.java b/service/mdns/com/android/server/connectivity/mdns/MdnsSocketProvider.java
index b8c324e..d3bf060 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsSocketProvider.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsSocketProvider.java
@@ -244,7 +244,7 @@
// Try to join the group again.
socketInfo.mSocket.joinGroup(addresses);
- notifyAddressesChanged(network, lp);
+ notifyAddressesChanged(network, socketInfo.mSocket, lp);
}
}
@@ -355,12 +355,13 @@
}
}
- private void notifyAddressesChanged(Network network, LinkProperties lp) {
+ private void notifyAddressesChanged(Network network, MdnsInterfaceSocket socket,
+ LinkProperties lp) {
for (int i = 0; i < mCallbacksToRequestedNetworks.size(); i++) {
final Network requestedNetwork = mCallbacksToRequestedNetworks.valueAt(i);
if (isNetworkMatched(requestedNetwork, network)) {
mCallbacksToRequestedNetworks.keyAt(i)
- .onAddressesChanged(network, lp.getLinkAddresses());
+ .onAddressesChanged(network, socket, lp.getLinkAddresses());
}
}
}
@@ -455,6 +456,6 @@
@NonNull MdnsInterfaceSocket socket) {}
/*** Notify the addresses is changed on the network */
default void onAddressesChanged(@NonNull Network network,
- @NonNull List<LinkAddress> addresses) {}
+ @NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> addresses) {}
}
}
diff --git a/service/mdns/com/android/server/connectivity/mdns/NameConflictException.java b/service/mdns/com/android/server/connectivity/mdns/NameConflictException.java
new file mode 100644
index 0000000..c123d02
--- /dev/null
+++ b/service/mdns/com/android/server/connectivity/mdns/NameConflictException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 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.connectivity.mdns;
+
+/**
+ * An exception thrown when a service name conflicts with an existing service.
+ */
+public class NameConflictException extends Exception {
+ /**
+ * ID of the existing service that conflicted.
+ */
+ public final int conflictingServiceId;
+ public NameConflictException(int conflictingServiceId) {
+ this.conflictingServiceId = conflictingServiceId;
+ }
+}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 004b4d2..8107be3 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -4366,6 +4366,9 @@
mNetworkForNetId.remove(nai.network.getNetId());
}
propagateUnderlyingNetworkCapabilities(nai.network);
+ // Update allowed network lists in netd. This should be called after removing nai
+ // from mNetworkAgentInfos.
+ updateProfileAllowedNetworks();
// Remove all previously satisfied requests.
for (int i = 0; i < nai.numNetworkRequests(); i++) {
final NetworkRequest request = nai.requestAt(i);
@@ -4800,6 +4803,7 @@
}
}
}
+
nri.mPerUidCounter.decrementCount(nri.mUid);
mNetworkRequestInfoLogs.log("RELEASE " + nri);
checkNrisConsistency(nri);
@@ -6166,12 +6170,16 @@
if (mOemNetworkPreferences.getNetworkPreferences().size() > 0) {
handleSetOemNetworkPreference(mOemNetworkPreferences, null);
}
+ if (!mProfileNetworkPreferences.isEmpty()) {
+ updateProfileAllowedNetworks();
+ }
}
private void onUserRemoved(@NonNull final UserHandle user) {
// If there was a network preference for this user, remove it.
handleSetProfileNetworkPreference(
- List.of(new ProfileNetworkPreferenceInfo(user, null, true)),
+ List.of(new ProfileNetworkPreferenceInfo(user, null, true,
+ false /* blockingNonEnterprise */)),
null /* listener */);
if (mOemNetworkPreferences.getNetworkPreferences().size() > 0) {
handleSetOemNetworkPreference(mOemNetworkPreferences, null);
@@ -8688,6 +8696,73 @@
}
}
+ /**
+ * Collect restricted uid ranges for the given network and UserHandle, these uids
+ * are not restricted for matched enterprise networks but being restricted for non-matched
+ * enterprise networks and non-enterprise networks.
+ */
+ @NonNull
+ private ArraySet<UidRange> getRestrictedUidRangesForEnterpriseBlocking(
+ @NonNull NetworkAgentInfo nai, @NonNull UserHandle user) {
+ final ArraySet<UidRange> restrictedUidRanges = new ArraySet<>();
+ for (final ProfileNetworkPreferenceInfo pref : mProfileNetworkPreferences) {
+ if (!pref.user.equals(user) || !pref.blockingNonEnterprise) continue;
+
+ if (nai.networkCapabilities.hasCapability(NET_CAPABILITY_ENTERPRISE)) {
+ // The NC is built from a `ProfileNetworkPreference` which has only one
+ // enterprise ID, so it's guaranteed to have exactly one.
+ final int prefId = pref.capabilities.getEnterpriseIds()[0];
+ if (nai.networkCapabilities.hasEnterpriseId(prefId)) {
+ continue;
+ }
+ }
+
+ if (UidRangeUtils.doesRangeSetOverlap(restrictedUidRanges,
+ pref.capabilities.getUidRanges())) {
+ throw new IllegalArgumentException(
+ "Overlapping uid range in preference: " + pref);
+ }
+ restrictedUidRanges.addAll(pref.capabilities.getUidRanges());
+ }
+ return restrictedUidRanges;
+ }
+
+ private void updateProfileAllowedNetworks() {
+ ensureRunningOnConnectivityServiceThread();
+ final ArrayList<NativeUidRangeConfig> configs = new ArrayList<>();
+ final List<UserHandle> users = mContext.getSystemService(UserManager.class)
+ .getUserHandles(true /* excludeDying */);
+ if (users.isEmpty()) {
+ throw new IllegalStateException("No user is available");
+ }
+
+ for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
+ ArraySet<UidRange> allowedUidRanges = new ArraySet<>();
+ for (final UserHandle user : users) {
+ final ArraySet<UidRange> restrictedUidRanges =
+ getRestrictedUidRangesForEnterpriseBlocking(nai, user);
+ allowedUidRanges.addAll(UidRangeUtils.removeRangeSetFromUidRange(
+ UidRange.createForUser(user), restrictedUidRanges));
+ }
+
+ final UidRangeParcel[] rangesParcel = toUidRangeStableParcels(allowedUidRanges);
+ configs.add(new NativeUidRangeConfig(
+ nai.network.netId, rangesParcel, 0 /* subPriority */));
+ }
+
+ // The netd API replaces the previous configs with the current configs.
+ // Thus, for network disconnection or preference removal, no need to
+ // unset previous config. Instead, collecting all currently needed
+ // configs and issue to netd.
+ try {
+ mNetd.setNetworkAllowlist(configs.toArray(new NativeUidRangeConfig[0]));
+ } catch (ServiceSpecificException e) {
+ // Has the interface disappeared since the network was built?
+ } catch (RemoteException e) {
+ // Netd died. This usually causes a runtime restart anyway.
+ }
+ }
+
private void makeDefaultNetwork(@Nullable final NetworkAgentInfo newDefaultNetwork) {
try {
if (null != newDefaultNetwork) {
@@ -9320,6 +9395,7 @@
networkAgent.setCreated();
networkAgent.onNetworkCreated();
updateAllowedUids(networkAgent, null, networkAgent.networkCapabilities);
+ updateProfileAllowedNetworks();
}
if (!networkAgent.everConnected() && state == NetworkInfo.State.CONNECTED) {
@@ -10856,6 +10932,7 @@
for (final ProfileNetworkPreference preference : preferences) {
final NetworkCapabilities nc;
boolean allowFallback = true;
+ boolean blockingNonEnterprise = false;
switch (preference.getPreference()) {
case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT:
nc = null;
@@ -10865,6 +10942,9 @@
"Invalid enterprise identifier in setProfileNetworkPreferences");
}
break;
+ case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING:
+ blockingNonEnterprise = true;
+ // continue to process the enterprise preference.
case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK:
allowFallback = false;
// continue to process the enterprise preference.
@@ -10898,7 +10978,8 @@
throw new IllegalArgumentException(
"Invalid preference in setProfileNetworkPreferences");
}
- preferenceList.add(new ProfileNetworkPreferenceInfo(profile, nc, allowFallback));
+ preferenceList.add(new ProfileNetworkPreferenceInfo(
+ profile, nc, allowFallback, blockingNonEnterprise));
if (hasDefaultPreference && preferenceList.size() > 1) {
throw new IllegalArgumentException(
"Default profile preference should not be set along with other preference");
@@ -11011,6 +11092,7 @@
removeDefaultNetworkRequestsForPreference(PREFERENCE_ORDER_PROFILE);
addPerAppDefaultNetworkRequests(
createNrisFromProfileNetworkPreferences(mProfileNetworkPreferences));
+ updateProfileAllowedNetworks();
// Finally, rematch.
rematchAllNetworksAndRequests();
diff --git a/service/src/com/android/server/connectivity/ProfileNetworkPreferenceInfo.java b/service/src/com/android/server/connectivity/ProfileNetworkPreferenceInfo.java
index 10f3886..7679660 100644
--- a/service/src/com/android/server/connectivity/ProfileNetworkPreferenceInfo.java
+++ b/service/src/com/android/server/connectivity/ProfileNetworkPreferenceInfo.java
@@ -32,13 +32,15 @@
@Nullable
public final NetworkCapabilities capabilities;
public final boolean allowFallback;
+ public final boolean blockingNonEnterprise;
public ProfileNetworkPreferenceInfo(@NonNull final UserHandle user,
@Nullable final NetworkCapabilities capabilities,
- final boolean allowFallback) {
+ final boolean allowFallback, final boolean blockingNonEnterprise) {
this.user = user;
this.capabilities = null == capabilities ? null : new NetworkCapabilities(capabilities);
this.allowFallback = allowFallback;
+ this.blockingNonEnterprise = blockingNonEnterprise;
}
@Override
@@ -57,6 +59,7 @@
return "[ProfileNetworkPreference user=" + user
+ " caps=" + capabilities
+ " allowFallback=" + allowFallback
+ + " blockingNonEnterprise=" + blockingNonEnterprise
+ "]";
}
}
diff --git a/tests/cts/OWNERS b/tests/cts/OWNERS
index 089d06f..8388cb7 100644
--- a/tests/cts/OWNERS
+++ b/tests/cts/OWNERS
@@ -2,6 +2,5 @@
set noparent
file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking_xts
-# Only temporary ownership to improve ethernet code quality (b/236280707)
-# TODO: remove by 12/31/2022
-per-file net/src/android/net/cts/EthernetManagerTest.kt = prohr@google.com #{LAST_RESORT_SUGGESTION}
+# IPsec
+per-file **IpSec* = benedictwong@google.com, nharold@google.com
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 3d6ee09..ca6a14b 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -64,6 +64,7 @@
import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
@@ -1058,15 +1059,30 @@
* @param validated Indicate if network should pretend to be validated.
*/
public void connect(boolean validated) {
- connect(validated, true, false /* isStrictMode */);
+ connect(validated, true, false /* privateDnsProbeSent */);
}
/**
* Transition this NetworkAgent to CONNECTED state.
+ *
* @param validated Indicate if network should pretend to be validated.
+ * Note that if this is true, this method will mock the NetworkMonitor
+ * probes to pretend the network is invalid after it validated once,
+ * so that subsequent attempts (with mNetworkMonitor.forceReevaluation)
+ * will fail unless setNetworkValid is called again manually.
* @param hasInternet Indicate if network should pretend to have NET_CAPABILITY_INTERNET.
+ * @param privateDnsProbeSent whether the private DNS probe should be considered to have
+ * been sent, assuming |validated| is true.
+ * If |validated| is false, |privateDnsProbeSent| is not used.
+ * If |validated| is true and |privateDnsProbeSent| is false,
+ * the probe has not been sent.
+ * If |validated| is true and |privateDnsProbeSent| is true,
+ * the probe has been sent and has succeeded. When the NM probes
+ * are mocked to be invalid, private DNS is the reason this
+ * network is invalid ; see @param |validated|.
*/
- public void connect(boolean validated, boolean hasInternet, boolean isStrictMode) {
+ public void connect(boolean validated, boolean hasInternet,
+ boolean privateDnsProbeSent) {
final ConditionVariable validatedCv = new ConditionVariable();
final ConditionVariable capsChangedCv = new ConditionVariable();
final NetworkRequest request = new NetworkRequest.Builder()
@@ -1074,7 +1090,7 @@
.clearCapabilities()
.build();
if (validated) {
- setNetworkValid(isStrictMode);
+ setNetworkValid(privateDnsProbeSent);
}
final NetworkCallback callback = new NetworkCallback() {
public void onCapabilitiesChanged(Network network,
@@ -1099,14 +1115,15 @@
if (validated) {
// Wait for network to validate.
waitFor(validatedCv);
- setNetworkInvalid(isStrictMode);
+ setNetworkInvalid(privateDnsProbeSent);
}
mCm.unregisterNetworkCallback(callback);
}
- public void connectWithCaptivePortal(String redirectUrl, boolean isStrictMode) {
- setNetworkPortal(redirectUrl, isStrictMode);
- connect(false, true /* hasInternet */, isStrictMode);
+ public void connectWithCaptivePortal(String redirectUrl,
+ boolean privateDnsProbeSent) {
+ setNetworkPortal(redirectUrl, privateDnsProbeSent);
+ connect(false, true /* hasInternet */, privateDnsProbeSent);
}
public void connectWithPartialConnectivity() {
@@ -1114,16 +1131,16 @@
connect(false);
}
- public void connectWithPartialValidConnectivity(boolean isStrictMode) {
- setNetworkPartialValid(isStrictMode);
- connect(false, true /* hasInternet */, isStrictMode);
+ public void connectWithPartialValidConnectivity(boolean privateDnsProbeSent) {
+ setNetworkPartialValid(privateDnsProbeSent);
+ connect(false, true /* hasInternet */, privateDnsProbeSent);
}
- void setNetworkValid(boolean isStrictMode) {
+ void setNetworkValid(boolean privateDnsProbeSent) {
mNmValidationResult = NETWORK_VALIDATION_RESULT_VALID;
mNmValidationRedirectUrl = null;
int probesSucceeded = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS;
- if (isStrictMode) {
+ if (privateDnsProbeSent) {
probesSucceeded |= NETWORK_VALIDATION_PROBE_PRIVDNS;
}
// The probesCompleted equals to probesSucceeded for the case of valid network, so put
@@ -1131,15 +1148,16 @@
setProbesStatus(probesSucceeded, probesSucceeded);
}
- void setNetworkInvalid(boolean isStrictMode) {
+ void setNetworkInvalid(boolean invalidBecauseOfPrivateDns) {
mNmValidationResult = VALIDATION_RESULT_INVALID;
mNmValidationRedirectUrl = null;
int probesCompleted = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS
| NETWORK_VALIDATION_PROBE_HTTP;
int probesSucceeded = 0;
- // If the isStrictMode is true, it means the network is invalid when NetworkMonitor
- // tried to validate the private DNS but failed.
- if (isStrictMode) {
+ // If |invalidBecauseOfPrivateDns| is true, it means the network is invalid because
+ // NetworkMonitor tried to validate the private DNS but failed. Therefore it
+ // didn't get a chance to try the HTTP probe.
+ if (invalidBecauseOfPrivateDns) {
probesCompleted &= ~NETWORK_VALIDATION_PROBE_HTTP;
probesSucceeded = probesCompleted;
probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS;
@@ -1147,14 +1165,14 @@
setProbesStatus(probesCompleted, probesSucceeded);
}
- void setNetworkPortal(String redirectUrl, boolean isStrictMode) {
- setNetworkInvalid(isStrictMode);
+ void setNetworkPortal(String redirectUrl, boolean privateDnsProbeSent) {
+ setNetworkInvalid(privateDnsProbeSent);
mNmValidationRedirectUrl = redirectUrl;
// Suppose the portal is found when NetworkMonitor probes NETWORK_VALIDATION_PROBE_HTTP
// in the beginning, so the NETWORK_VALIDATION_PROBE_HTTPS hasn't probed yet.
int probesCompleted = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP;
int probesSucceeded = VALIDATION_RESULT_INVALID;
- if (isStrictMode) {
+ if (privateDnsProbeSent) {
probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS;
}
setProbesStatus(probesCompleted, probesSucceeded);
@@ -1169,7 +1187,7 @@
setProbesStatus(probesCompleted, probesSucceeded);
}
- void setNetworkPartialValid(boolean isStrictMode) {
+ void setNetworkPartialValid(boolean privateDnsProbeSent) {
setNetworkPartial();
mNmValidationResult |= NETWORK_VALIDATION_RESULT_VALID;
mNmValidationRedirectUrl = null;
@@ -1178,7 +1196,7 @@
int probesSucceeded = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP;
// Assume the partial network cannot pass the private DNS validation as well, so only
// add NETWORK_VALIDATION_PROBE_DNS in probesCompleted but not probesSucceeded.
- if (isStrictMode) {
+ if (privateDnsProbeSent) {
probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS;
}
setProbesStatus(probesCompleted, probesSucceeded);
@@ -1473,8 +1491,9 @@
registerAgent(false /* isAlwaysMetered */, uids, makeLinkProperties());
}
- private void connect(boolean validated, boolean hasInternet, boolean isStrictMode) {
- mMockNetworkAgent.connect(validated, hasInternet, isStrictMode);
+ private void connect(boolean validated, boolean hasInternet,
+ boolean privateDnsProbeSent) {
+ mMockNetworkAgent.connect(validated, hasInternet, privateDnsProbeSent);
}
private void connect(boolean validated) {
@@ -1491,10 +1510,10 @@
}
public void establish(LinkProperties lp, int uid, Set<UidRange> ranges, boolean validated,
- boolean hasInternet, boolean isStrictMode) throws Exception {
+ boolean hasInternet, boolean privateDnsProbeSent) throws Exception {
setOwnerAndAdminUid(uid);
registerAgent(false, ranges, lp);
- connect(validated, hasInternet, isStrictMode);
+ connect(validated, hasInternet, privateDnsProbeSent);
waitForIdle();
}
@@ -1507,11 +1526,11 @@
establish(lp, uid, uidRangesForUids(uid), true, true, false);
}
- public void establishForMyUid(boolean validated, boolean hasInternet, boolean isStrictMode)
- throws Exception {
+ public void establishForMyUid(boolean validated, boolean hasInternet,
+ boolean privateDnsProbeSent) throws Exception {
final int uid = Process.myUid();
establish(makeLinkProperties(), uid, uidRangesForUids(uid), validated, hasInternet,
- isStrictMode);
+ privateDnsProbeSent);
}
public void establishForMyUid() throws Exception {
@@ -4219,7 +4238,7 @@
// With HTTPS probe disabled, NetworkMonitor should pass the network validation with http
// probe.
- mWiFiNetworkAgent.setNetworkPartialValid(false /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkPartialValid(false /* privateDnsProbeSent */);
// If the user chooses yes to use this partial connectivity wifi, switch the default
// network to wifi and check if wifi becomes valid or not.
mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */,
@@ -4306,7 +4325,7 @@
callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
expectUnvalidationCheckWillNotNotify(mWiFiNetworkAgent);
- mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkValid(false /* privateDnsProbeSent */);
// Need a trigger point to let NetworkMonitor tell ConnectivityService that the network is
// validated.
@@ -4324,7 +4343,8 @@
// NetworkMonitor will immediately (once the HTTPS probe fails...) report the network as
// valid, because ConnectivityService calls setAcceptPartialConnectivity before it calls
// notifyNetworkConnected.
- mWiFiNetworkAgent.connectWithPartialValidConnectivity(false /* isStrictMode */);
+ mWiFiNetworkAgent.connectWithPartialValidConnectivity(
+ false /* privateDnsProbeSent */);
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
callback.expectLosing(mCellNetworkAgent);
@@ -4353,7 +4373,8 @@
// Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
String redirectUrl = "http://android.com/path";
- mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl, false /* isStrictMode */);
+ mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl,
+ false /* privateDnsProbeSent */);
wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), redirectUrl);
@@ -4378,7 +4399,7 @@
&& !nc.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL));
// Report partial connectivity is accepted.
- mWiFiNetworkAgent.setNetworkPartialValid(false /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkPartialValid(false /* privateDnsProbeSent */);
mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */,
false /* always */);
waitForIdle();
@@ -4408,7 +4429,8 @@
// Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
String firstRedirectUrl = "http://example.com/firstPath";
- mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl, false /* isStrictMode */);
+ mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl,
+ false /* privateDnsProbeSent */);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl);
@@ -4421,13 +4443,14 @@
// Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
String secondRedirectUrl = "http://example.com/secondPath";
- mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl, false /* isStrictMode */);
+ mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl,
+ false /* privateDnsProbeSent */);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), secondRedirectUrl);
// Make captive portal disappear then revalidate.
// Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL.
- mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkValid(false /* privateDnsProbeSent */);
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
captivePortalCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
@@ -4436,7 +4459,7 @@
// Break network connectivity.
// Expect NET_CAPABILITY_VALIDATED onLost callback.
- mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkInvalid(false /* invalidBecauseOfPrivateDns */);
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
validatedCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
}
@@ -4488,7 +4511,8 @@
mServiceContext.expectNoStartActivityIntent(fastTimeoutMs);
// Turn into a captive portal.
- mWiFiNetworkAgent.setNetworkPortal("http://example.com", false /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkPortal("http://example.com",
+ false /* privateDnsProbeSent */);
mCm.reportNetworkConnectivity(wifiNetwork, false);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
validatedCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
@@ -4501,7 +4525,7 @@
startCaptivePortalApp(mWiFiNetworkAgent);
// Report that the captive portal is dismissed, and check that callbacks are fired
- mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkValid(false /* privateDnsProbeSent */);
mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
captivePortalCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
@@ -4559,7 +4583,8 @@
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
String firstRedirectUrl = "http://example.com/firstPath";
- mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl, false /* isStrictMode */);
+ mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl,
+ false /* privateDnsProbeSent */);
mWiFiNetworkAgent.expectDisconnected();
mWiFiNetworkAgent.expectPreventReconnectReceived();
@@ -4578,7 +4603,8 @@
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
final String redirectUrl = "http://example.com/firstPath";
- mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl, false /* isStrictMode */);
+ mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl,
+ false /* privateDnsProbeSent */);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
final CaptivePortalData testData = new CaptivePortalData.Builder()
@@ -4611,7 +4637,8 @@
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
- mWiFiNetworkAgent.connectWithCaptivePortal(TEST_REDIRECT_URL, false /* isStrictMode */);
+ mWiFiNetworkAgent.connectWithCaptivePortal(TEST_REDIRECT_URL,
+ false /* privateDnsProbeSent */);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
return captivePortalCallback;
}
@@ -5812,7 +5839,7 @@
wifiCallback.assertNoCallback();
// Wifi validates. Cell is no longer needed, because it's outscored.
- mWiFiNetworkAgent.setNetworkValid(true /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkValid(true /* privateDnsProbeSent */);
// Have CS reconsider the network (see testPartialConnectivity)
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
wifiNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
@@ -5820,7 +5847,7 @@
wifiCallback.assertNoCallback();
// Wifi is no longer validated. Cell is needed again.
- mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkInvalid(true /* invalidBecauseOfPrivateDns */);
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
wifiNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
cellCallback.expectOnNetworkNeeded(defaultCaps);
@@ -5842,7 +5869,7 @@
wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
cellCallback.assertNoCallback();
wifiCallback.assertNoCallback();
- mWiFiNetworkAgent.setNetworkValid(true /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkValid(true /* privateDnsProbeSent */);
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
wifiNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
cellCallback.expectOnNetworkUnneeded(defaultCaps);
@@ -5850,7 +5877,7 @@
// Wifi loses validation. Because the device doesn't avoid bad wifis, cell is
// not needed.
- mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkInvalid(true /* invalidBecauseOfPrivateDns */);
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
wifiNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
cellCallback.assertNoCallback();
@@ -5945,7 +5972,7 @@
Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
// Fail validation on wifi.
- mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkInvalid(false /* invalidBecauseOfPrivateDns */);
mCm.reportNetworkConnectivity(wifiNetwork, false);
defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
validatedWifiCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
@@ -5996,7 +6023,7 @@
wifiNetwork = mWiFiNetworkAgent.getNetwork();
// Fail validation on wifi and expect the dialog to appear.
- mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkInvalid(false /* invalidBecauseOfPrivateDns */);
mCm.reportNetworkConnectivity(wifiNetwork, false);
defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
validatedWifiCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
@@ -7155,7 +7182,7 @@
mCm.registerNetworkCallback(request, callback);
// Bring up wifi aware network.
- wifiAware.connect(false, false, false /* isStrictMode */);
+ wifiAware.connect(false, false, false /* privateDnsProbeSent */);
callback.expectAvailableCallbacksUnvalidated(wifiAware);
assertNull(mCm.getActiveNetworkInfo());
@@ -7733,7 +7760,7 @@
mWiFiNetworkAgent.connect(false);
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
// Private DNS resolution failed, checking if the notification will be shown or not.
- mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkInvalid(true /* invalidBecauseOfPrivateDns */);
mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
waitForIdle();
// If network validation failed, NetworkMonitor will re-evaluate the network.
@@ -7745,14 +7772,14 @@
eq(NotificationType.PRIVATE_DNS_BROKEN.eventId), any());
// If private DNS resolution successful, the PRIVATE_DNS_BROKEN notification shouldn't be
// shown.
- mWiFiNetworkAgent.setNetworkValid(true /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkValid(true /* privateDnsProbeSent */);
mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
waitForIdle();
verify(mNotificationManager, timeout(TIMEOUT_MS).times(1)).cancel(anyString(),
eq(NotificationType.PRIVATE_DNS_BROKEN.eventId));
// If private DNS resolution failed again, the PRIVATE_DNS_BROKEN notification should be
// shown again.
- mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkInvalid(true /* invalidBecauseOfPrivateDns */);
mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
waitForIdle();
verify(mNotificationManager, timeout(TIMEOUT_MS).times(2)).notify(anyString(),
@@ -8214,7 +8241,7 @@
// Connect a VPN.
mMockVpn.establishForMyUid(false /* validated */, true /* hasInternet */,
- false /* isStrictMode */);
+ false /* privateDnsProbeSent */);
callback.expectAvailableCallbacksUnvalidated(mMockVpn);
// Connect cellular data.
@@ -8370,7 +8397,7 @@
NetworkAgentConfigShimImpl.newInstance(mMockVpn.getNetworkAgentConfig())
.isVpnValidationRequired(),
mMockVpn.getAgent().getNetworkCapabilities()));
- mMockVpn.getAgent().setNetworkValid(false /* isStrictMode */);
+ mMockVpn.getAgent().setNetworkValid(false /* privateDnsProbeSent */);
mMockVpn.connect(false);
@@ -8453,7 +8480,7 @@
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
- false /* isStrictMode */);
+ false /* privateDnsProbeSent */);
assertUidRangesUpdatedForMyUid(true);
defaultCallback.assertNoCallback();
@@ -8479,7 +8506,7 @@
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
mMockVpn.establishForMyUid(true /* validated */, true /* hasInternet */,
- false /* isStrictMode */);
+ false /* privateDnsProbeSent */);
assertUidRangesUpdatedForMyUid(true);
defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
@@ -8505,7 +8532,7 @@
// Bring up a VPN that has the INTERNET capability, initially unvalidated.
mMockVpn.establishForMyUid(false /* validated */, true /* hasInternet */,
- false /* isStrictMode */);
+ false /* privateDnsProbeSent */);
assertUidRangesUpdatedForMyUid(true);
// Even though the VPN is unvalidated, it becomes the default network for our app.
@@ -8527,7 +8554,7 @@
mMockVpn.getAgent().getNetworkCapabilities()));
// Pretend that the VPN network validates.
- mMockVpn.getAgent().setNetworkValid(false /* isStrictMode */);
+ mMockVpn.getAgent().setNetworkValid(false /* privateDnsProbeSent */);
mMockVpn.getAgent().mNetworkMonitor.forceReevaluation(Process.myUid());
// Expect to see the validated capability, but no other changes, because the VPN is already
// the default network for the app.
@@ -8558,7 +8585,7 @@
mCellNetworkAgent.connect(true);
mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
- false /* isStrictMode */);
+ false /* privateDnsProbeSent */);
assertUidRangesUpdatedForMyUid(true);
vpnNetworkCallback.expectAvailableCallbacks(mMockVpn.getNetwork(),
@@ -8602,7 +8629,7 @@
vpnNetworkCallback.assertNoCallback();
mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
- false /* isStrictMode */);
+ false /* privateDnsProbeSent */);
assertUidRangesUpdatedForMyUid(true);
vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
@@ -8767,7 +8794,7 @@
vpnNetworkCallback.assertNoCallback();
mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
- false /* isStrictMode */);
+ false /* privateDnsProbeSent */);
assertUidRangesUpdatedForMyUid(true);
vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
@@ -10402,6 +10429,7 @@
verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
eq(Integer.toString(TRANSPORT_CELLULAR)));
verify(mMockNetd).networkDestroy(cellNetId);
+ verify(mMockNetd).setNetworkAllowlist(any());
verifyNoMoreInteractions(mMockNetd);
verifyNoMoreInteractions(mClatCoordinator);
reset(mMockNetd);
@@ -10442,6 +10470,7 @@
verify(mMockNetd).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
eq(Integer.toString(TRANSPORT_CELLULAR)));
verify(mMockNetd).networkDestroy(cellNetId);
+ verify(mMockNetd).setNetworkAllowlist(any());
verifyNoMoreInteractions(mMockNetd);
verifyNoMoreInteractions(mClatCoordinator);
@@ -12312,7 +12341,8 @@
b1.expectNoBroadcast(500);
final ExpectedBroadcast b2 = registerPacProxyBroadcast();
- mMockVpn.connect(true /* validated */, true /* hasInternet */, false /* isStrictMode */);
+ mMockVpn.connect(true /* validated */, true /* hasInternet */,
+ false /* privateDnsProbeSent */);
waitForIdle();
assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID);
// Vpn is connected with proxy, so the proxy broadcast will be sent to inform the apps to
@@ -14810,7 +14840,7 @@
// Make sure changes to the work agent send callbacks to the app in the work profile, but
// not to the other apps.
- workAgent.setNetworkValid(true /* isStrictMode */);
+ workAgent.setNetworkValid(true /* privateDnsProbeSent */);
workAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
profileDefaultNetworkCallback.expectCapabilitiesThat(workAgent,
nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED)
@@ -14923,7 +14953,7 @@
workAgent2.getNetwork().netId,
uidRangeFor(testHandle, profileNetworkPreference), PREFERENCE_ORDER_PROFILE));
- workAgent2.setNetworkValid(true /* isStrictMode */);
+ workAgent2.setNetworkValid(true /* privateDnsProbeSent */);
workAgent2.mNetworkMonitor.forceReevaluation(Process.myUid());
profileDefaultNetworkCallback.expectCapabilitiesThat(workAgent2,
nc -> nc.hasCapability(NET_CAPABILITY_ENTERPRISE)
@@ -15752,6 +15782,171 @@
PREFERENCE_ORDER_PROFILE));
}
+ @Test
+ public void testProfileNetworkPreferenceBlocking_changePreference() throws Exception {
+ final InOrder inOrder = inOrder(mMockNetd);
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ doReturn(asList(PRIMARY_USER_HANDLE, testHandle))
+ .when(mUserManager).getUserHandles(anyBoolean());
+
+ // Start with 1 default network and 1 enterprise network, both networks should
+ // not be restricted since the blocking preference is not set yet.
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+
+ // Verify uid ranges 0~99999, 200000~299999 are all allowed for cellular.
+ final UidRange profileUidRange =
+ UidRange.createForUser(UserHandle.of(TEST_WORK_PROFILE_USER_ID));
+ ArraySet<UidRange> allowedAllUidRanges = new ArraySet<>();
+ allowedAllUidRanges.add(PRIMARY_UIDRANGE);
+ allowedAllUidRanges.add(profileUidRange);
+ final UidRangeParcel[] allowAllUidRangesParcel = toUidRangeStableParcels(
+ allowedAllUidRanges);
+ final NativeUidRangeConfig cellAllAllowedConfig = new NativeUidRangeConfig(
+ mCellNetworkAgent.getNetwork().netId,
+ allowAllUidRangesParcel,
+ 0 /* subPriority */);
+ inOrder.verify(mMockNetd).setNetworkAllowlist(
+ new NativeUidRangeConfig[]{cellAllAllowedConfig});
+
+ // Verify the same uid ranges are also applied for enterprise network.
+ final TestNetworkAgentWrapper enterpriseAgent = makeEnterpriseNetworkAgent(
+ NET_ENTERPRISE_ID_1);
+ enterpriseAgent.connect(true);
+ final NativeUidRangeConfig enterpriseAllAllowedConfig = new NativeUidRangeConfig(
+ enterpriseAgent.getNetwork().netId,
+ allowAllUidRangesParcel,
+ 0 /* subPriority */);
+ // Network agents are stored in an ArraySet which does not guarantee the order and
+ // making the order of the list undeterministic. Thus, verify this in order insensitive way.
+ final ArgumentCaptor<NativeUidRangeConfig[]> configsCaptor = ArgumentCaptor.forClass(
+ NativeUidRangeConfig[].class);
+ inOrder.verify(mMockNetd).setNetworkAllowlist(configsCaptor.capture());
+ assertContainsAll(List.of(configsCaptor.getValue()),
+ List.of(cellAllAllowedConfig, enterpriseAllAllowedConfig));
+
+ // Setup profile preference which only applies to test app uid on the managed profile.
+ ProfileNetworkPreference.Builder prefBuilder = new ProfileNetworkPreference.Builder();
+ prefBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING)
+ .setIncludedUids(new int[]{testHandle.getUid(TEST_WORK_PROFILE_APP_UID)})
+ .setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ final TestOnCompleteListener listener = new TestOnCompleteListener();
+ mCm.setProfileNetworkPreferences(testHandle,
+ List.of(prefBuilder.build()),
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+
+ // Verify Netd is called for the preferences changed.
+ // Cell: 0~99999, 200000~TEST_APP_UID-1, TEST_APP_UID+1~299999
+ // Enterprise: 0~99999, 200000~299999
+ final ArraySet<UidRange> excludeAppRanges = new ArraySet<>();
+ excludeAppRanges.add(PRIMARY_UIDRANGE);
+ excludeAppRanges.addAll(UidRangeUtils.removeRangeSetFromUidRange(
+ profileUidRange,
+ new ArraySet(new UidRange[]{
+ (new UidRange(TEST_WORK_PROFILE_APP_UID, TEST_WORK_PROFILE_APP_UID))})
+ ));
+ final UidRangeParcel[] excludeAppRangesParcel = toUidRangeStableParcels(excludeAppRanges);
+ final NativeUidRangeConfig cellExcludeAppConfig = new NativeUidRangeConfig(
+ mCellNetworkAgent.getNetwork().netId,
+ excludeAppRangesParcel,
+ 0 /* subPriority */);
+ inOrder.verify(mMockNetd).setNetworkAllowlist(configsCaptor.capture());
+ assertContainsAll(List.of(configsCaptor.getValue()),
+ List.of(cellExcludeAppConfig, enterpriseAllAllowedConfig));
+
+ // Verify unset by giving all allowed set for all users when the preference got removed.
+ mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+ inOrder.verify(mMockNetd).setNetworkAllowlist(configsCaptor.capture());
+ assertContainsAll(List.of(configsCaptor.getValue()),
+ List.of(cellAllAllowedConfig, enterpriseAllAllowedConfig));
+
+ // Verify issuing with cellular set only when a network with enterprise capability
+ // disconnects.
+ enterpriseAgent.disconnect();
+ waitForIdle();
+ inOrder.verify(mMockNetd).setNetworkAllowlist(
+ new NativeUidRangeConfig[]{cellAllAllowedConfig});
+ }
+
+ @Test
+ public void testProfileNetworkPreferenceBlocking_networkChanges() throws Exception {
+ final InOrder inOrder = inOrder(mMockNetd);
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ doReturn(asList(PRIMARY_USER_HANDLE, testHandle))
+ .when(mUserManager).getUserHandles(anyBoolean());
+
+ // Setup profile preference which only applies to test app uid on the managed profile.
+ ProfileNetworkPreference.Builder prefBuilder = new ProfileNetworkPreference.Builder();
+ prefBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING)
+ .setIncludedUids(new int[]{testHandle.getUid(TEST_WORK_PROFILE_APP_UID)})
+ .setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ final TestOnCompleteListener listener = new TestOnCompleteListener();
+ mCm.setProfileNetworkPreferences(testHandle,
+ List.of(prefBuilder.build()),
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+ inOrder.verify(mMockNetd).setNetworkAllowlist(new NativeUidRangeConfig[]{});
+
+ // Start with 1 default network, which should be restricted since the blocking
+ // preference is already set.
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+
+ // Verify cellular network applies to the allow list.
+ // Cell: 0~99999, 200000~TEST_APP_UID-1, TEST_APP_UID+1~299999
+ // Enterprise: 0~99999, 200000~299999
+ final ArraySet<UidRange> excludeAppRanges = new ArraySet<>();
+ final UidRange profileUidRange =
+ UidRange.createForUser(UserHandle.of(TEST_WORK_PROFILE_USER_ID));
+ excludeAppRanges.add(PRIMARY_UIDRANGE);
+ excludeAppRanges.addAll(UidRangeUtils.removeRangeSetFromUidRange(
+ profileUidRange,
+ new ArraySet(new UidRange[]{
+ (new UidRange(TEST_WORK_PROFILE_APP_UID, TEST_WORK_PROFILE_APP_UID))})
+ ));
+ final UidRangeParcel[] excludeAppRangesParcel = toUidRangeStableParcels(excludeAppRanges);
+ final NativeUidRangeConfig cellExcludeAppConfig = new NativeUidRangeConfig(
+ mCellNetworkAgent.getNetwork().netId,
+ excludeAppRangesParcel,
+ 0 /* subPriority */);
+ inOrder.verify(mMockNetd).setNetworkAllowlist(
+ new NativeUidRangeConfig[]{cellExcludeAppConfig});
+
+ // Verify enterprise network is not blocked for test app.
+ final TestNetworkAgentWrapper enterpriseAgent = makeEnterpriseNetworkAgent(
+ NET_ENTERPRISE_ID_1);
+ enterpriseAgent.connect(true);
+ ArraySet<UidRange> allowedAllUidRanges = new ArraySet<>();
+ allowedAllUidRanges.add(PRIMARY_UIDRANGE);
+ allowedAllUidRanges.add(profileUidRange);
+ final UidRangeParcel[] allowAllUidRangesParcel = toUidRangeStableParcels(
+ allowedAllUidRanges);
+ final NativeUidRangeConfig enterpriseAllAllowedConfig = new NativeUidRangeConfig(
+ enterpriseAgent.getNetwork().netId,
+ allowAllUidRangesParcel,
+ 0 /* subPriority */);
+ // Network agents are stored in an ArraySet which does not guarantee the order and
+ // making the order of the list undeterministic. Thus, verify this in order insensitive way.
+ final ArgumentCaptor<NativeUidRangeConfig[]> configsCaptor = ArgumentCaptor.forClass(
+ NativeUidRangeConfig[].class);
+ inOrder.verify(mMockNetd).setNetworkAllowlist(configsCaptor.capture());
+ assertContainsAll(List.of(configsCaptor.getValue()),
+ List.of(enterpriseAllAllowedConfig, cellExcludeAppConfig));
+
+ // Verify issuing with cellular set only when enterprise network disconnects.
+ enterpriseAgent.disconnect();
+ waitForIdle();
+ inOrder.verify(mMockNetd).setNetworkAllowlist(
+ new NativeUidRangeConfig[]{cellExcludeAppConfig});
+
+ mCellNetworkAgent.disconnect();
+ waitForIdle();
+ inOrder.verify(mMockNetd).setNetworkAllowlist(new NativeUidRangeConfig[]{});
+ }
+
/**
* Make sure wrong preferences for per-profile default networking are rejected.
*/
@@ -15762,7 +15957,7 @@
ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
new ProfileNetworkPreference.Builder();
profileNetworkPreferenceBuilder.setPreference(
- PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK + 1);
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING + 1);
profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
assertThrows("Should not be able to set an illegal preference",
IllegalArgumentException.class,
@@ -16923,7 +17118,8 @@
mWiFiNetworkAgent);
mDefaultNetworkCallback.expect(CallbackEntry.NETWORK_CAPS_UPDATED,
mWiFiNetworkAgent);
- mWiFiNetworkAgent.setNetworkPortal(TEST_REDIRECT_URL, false /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkPortal(TEST_REDIRECT_URL,
+ false /* privateDnsProbeSent */);
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
// Wi-Fi is now detected to have a portal : cell should become the default network.
mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
@@ -16933,7 +17129,7 @@
mWiFiNetworkAgent);
// Wi-Fi becomes valid again. The default network goes back to Wi-Fi.
- mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkValid(false /* privateDnsProbeSent */);
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
mDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
wifiNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_CAPTIVE_PORTAL,
@@ -16954,7 +17150,7 @@
mWiFiNetworkAgent);
// Wi-Fi becomes valid again. The default network goes back to Wi-Fi.
- mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkValid(false /* privateDnsProbeSent */);
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
mDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
wifiNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
@@ -16968,7 +17164,7 @@
mWiFiNetworkAgent.setNetworkCapabilities(wifiNc2, true);
mDefaultNetworkCallback.expect(CallbackEntry.NETWORK_CAPS_UPDATED,
mWiFiNetworkAgent);
- mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */);
+ mWiFiNetworkAgent.setNetworkInvalid(false /* invalidBecauseOfPrivateDns */);
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
if (enabled) {
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
new file mode 100644
index 0000000..e2babb1
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2022 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.connectivity.mdns
+
+import android.net.InetAddresses.parseNumericAddress
+import android.net.LinkAddress
+import android.net.Network
+import android.net.nsd.NsdServiceInfo
+import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
+import com.android.server.connectivity.mdns.MdnsAdvertiser.AdvertiserCallback
+import com.android.server.connectivity.mdns.MdnsSocketProvider.SocketCallback
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.waitForIdle
+import java.util.Objects
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+private const val SERVICE_ID_1 = 1
+private const val SERVICE_ID_2 = 2
+private const val TIMEOUT_MS = 10_000L
+private val TEST_ADDR = parseNumericAddress("2001:db8::123")
+private val TEST_LINKADDR = LinkAddress(TEST_ADDR, 64 /* prefixLength */)
+private val TEST_NETWORK_1 = mock(Network::class.java)
+private val TEST_NETWORK_2 = mock(Network::class.java)
+
+private val SERVICE_1 = NsdServiceInfo("TestServiceName", "_advertisertest._tcp").apply {
+ port = 12345
+ host = TEST_ADDR
+ network = TEST_NETWORK_1
+}
+
+private val ALL_NETWORKS_SERVICE = NsdServiceInfo("TestServiceName", "_advertisertest._tcp").apply {
+ port = 12345
+ host = TEST_ADDR
+ network = null
+}
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class MdnsAdvertiserTest {
+ private val thread = HandlerThread(MdnsAdvertiserTest::class.simpleName)
+ private val handler by lazy { Handler(thread.looper) }
+ private val socketProvider = mock(MdnsSocketProvider::class.java)
+ private val cb = mock(AdvertiserCallback::class.java)
+
+ private val mockSocket1 = mock(MdnsInterfaceSocket::class.java)
+ private val mockSocket2 = mock(MdnsInterfaceSocket::class.java)
+ private val mockInterfaceAdvertiser1 = mock(MdnsInterfaceAdvertiser::class.java)
+ private val mockInterfaceAdvertiser2 = mock(MdnsInterfaceAdvertiser::class.java)
+ private val mockDeps = mock(MdnsAdvertiser.Dependencies::class.java)
+
+ @Before
+ fun setUp() {
+ thread.start()
+ doReturn(mockInterfaceAdvertiser1).`when`(mockDeps).makeAdvertiser(eq(mockSocket1),
+ any(), any(), any(), any())
+ doReturn(mockInterfaceAdvertiser2).`when`(mockDeps).makeAdvertiser(eq(mockSocket2),
+ any(), any(), any(), any())
+ doReturn(true).`when`(mockInterfaceAdvertiser1).isProbing(anyInt())
+ doReturn(true).`when`(mockInterfaceAdvertiser2).isProbing(anyInt())
+ }
+
+ @After
+ fun tearDown() {
+ thread.quitSafely()
+ }
+
+ @Test
+ fun testAddService_OneNetwork() {
+ val advertiser = MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps)
+ postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1) }
+
+ val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
+ verify(socketProvider).requestSocket(eq(TEST_NETWORK_1), socketCbCaptor.capture())
+
+ val socketCb = socketCbCaptor.value
+ postSync { socketCb.onSocketCreated(TEST_NETWORK_1, mockSocket1, listOf(TEST_LINKADDR)) }
+
+ val intAdvCbCaptor = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
+ verify(mockDeps).makeAdvertiser(eq(mockSocket1),
+ eq(listOf(TEST_LINKADDR)), eq(thread.looper), any(), intAdvCbCaptor.capture())
+
+ doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
+ postSync { intAdvCbCaptor.value.onRegisterServiceSucceeded(
+ mockInterfaceAdvertiser1, SERVICE_ID_1) }
+ verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1), argThat { it.matches(SERVICE_1) })
+
+ postSync { socketCb.onInterfaceDestroyed(TEST_NETWORK_1, mockSocket1) }
+ verify(mockInterfaceAdvertiser1).destroyNow()
+ }
+
+ @Test
+ fun testAddService_AllNetworks() {
+ val advertiser = MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps)
+ postSync { advertiser.addService(SERVICE_ID_1, ALL_NETWORKS_SERVICE) }
+
+ val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
+ verify(socketProvider).requestSocket(eq(ALL_NETWORKS_SERVICE.network),
+ socketCbCaptor.capture())
+
+ val socketCb = socketCbCaptor.value
+ postSync { socketCb.onSocketCreated(TEST_NETWORK_1, mockSocket1, listOf(TEST_LINKADDR)) }
+ postSync { socketCb.onSocketCreated(TEST_NETWORK_2, mockSocket2, listOf(TEST_LINKADDR)) }
+
+ val intAdvCbCaptor1 = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
+ val intAdvCbCaptor2 = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
+ verify(mockDeps).makeAdvertiser(eq(mockSocket1), eq(listOf(TEST_LINKADDR)),
+ eq(thread.looper), any(), intAdvCbCaptor1.capture())
+ verify(mockDeps).makeAdvertiser(eq(mockSocket2), eq(listOf(TEST_LINKADDR)),
+ eq(thread.looper), any(), intAdvCbCaptor2.capture())
+
+ doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
+ postSync { intAdvCbCaptor1.value.onRegisterServiceSucceeded(
+ mockInterfaceAdvertiser1, SERVICE_ID_1) }
+
+ // Need both advertisers to finish probing and call onRegisterServiceSucceeded
+ verify(cb, never()).onRegisterServiceSucceeded(anyInt(), any())
+ doReturn(false).`when`(mockInterfaceAdvertiser2).isProbing(SERVICE_ID_1)
+ postSync { intAdvCbCaptor2.value.onRegisterServiceSucceeded(
+ mockInterfaceAdvertiser2, SERVICE_ID_1) }
+ verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1),
+ argThat { it.matches(ALL_NETWORKS_SERVICE) })
+
+ // Unregister the service
+ postSync { advertiser.removeService(SERVICE_ID_1) }
+ verify(mockInterfaceAdvertiser1).removeService(SERVICE_ID_1)
+ verify(mockInterfaceAdvertiser2).removeService(SERVICE_ID_1)
+
+ // Interface advertisers call onDestroyed after sending exit announcements
+ postSync { intAdvCbCaptor1.value.onDestroyed(mockSocket1) }
+ verify(socketProvider, never()).unrequestSocket(any())
+ postSync { intAdvCbCaptor2.value.onDestroyed(mockSocket2) }
+ verify(socketProvider).unrequestSocket(socketCb)
+ }
+
+ private fun postSync(r: () -> Unit) {
+ handler.post(r)
+ handler.waitForIdle(TIMEOUT_MS)
+ }
+}
+
+// NsdServiceInfo does not implement equals; this is useful to use in argument matchers
+private fun NsdServiceInfo.matches(other: NsdServiceInfo): Boolean {
+ return Objects.equals(serviceName, other.serviceName) &&
+ Objects.equals(serviceType, other.serviceType) &&
+ Objects.equals(attributes, other.attributes) &&
+ Objects.equals(host, other.host) &&
+ port == other.port &&
+ Objects.equals(network, other.network)
+}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
index e9325d5..2051e0c 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
@@ -28,7 +28,6 @@
import java.net.Inet6Address
import java.net.InetAddress
import java.net.InetSocketAddress
-import java.net.MulticastSocket
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import org.junit.After
@@ -55,7 +54,7 @@
class MdnsAnnouncerTest {
private val thread = HandlerThread(MdnsAnnouncerTest::class.simpleName)
- private val socket = mock(MulticastSocket::class.java)
+ private val socket = mock(MdnsInterfaceSocket::class.java)
private val buffer = ByteArray(1500)
@Before
@@ -71,8 +70,7 @@
private class TestAnnouncementInfo(
announcedRecords: List<MdnsRecord>,
additionalRecords: List<MdnsRecord>
- )
- : AnnouncementInfo(announcedRecords, additionalRecords, destinationsSupplier) {
+ ) : AnnouncementInfo(announcedRecords, additionalRecords, destinationsSupplier) {
override fun getDelayMs(nextIndex: Int) =
if (nextIndex < FIRST_ANNOUNCES_COUNT) {
FIRST_ANNOUNCES_DELAY
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
index 419121c..a98a4b2 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
@@ -26,7 +26,6 @@
import com.android.testutils.DevSdkIgnoreRunner
import java.net.DatagramPacket
import java.net.InetSocketAddress
-import java.net.MulticastSocket
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals
@@ -57,7 +56,7 @@
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
class MdnsProberTest {
private val thread = HandlerThread(MdnsProberTest::class.simpleName)
- private val socket = mock(MulticastSocket::class.java)
+ private val socket = mock(MdnsInterfaceSocket::class.java)
@Suppress("UNCHECKED_CAST")
private val cb = mock(MdnsPacketRepeater.PacketRepeaterCallback::class.java)
as MdnsPacketRepeater.PacketRepeaterCallback<ProbingInfo>
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
index 2bb61a6a..ef73030 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
@@ -159,7 +159,8 @@
}
@Override
- public void onAddressesChanged(Network network, List<LinkAddress> addresses) {
+ public void onAddressesChanged(Network network, MdnsInterfaceSocket socket,
+ List<LinkAddress> addresses) {
mHistory.add(new AddressesChangedEvent(network, addresses));
}
diff --git a/tools/gn2bp/Android.bp.swp b/tools/gn2bp/Android.bp.swp
index 708c9fd..804c2a4 100644
--- a/tools/gn2bp/Android.bp.swp
+++ b/tools/gn2bp/Android.bp.swp
@@ -35,7 +35,6 @@
"components/cronet/android/api/src/org/chromium/net/CallbackException.java",
"components/cronet/android/api/src/org/chromium/net/CronetEngine.java",
"components/cronet/android/api/src/org/chromium/net/CronetException.java",
- "components/cronet/android/api/src/org/chromium/net/CronetProvider.java",
"components/cronet/android/api/src/org/chromium/net/ExperimentalBidirectionalStream.java",
"components/cronet/android/api/src/org/chromium/net/ExperimentalCronetEngine.java",
"components/cronet/android/api/src/org/chromium/net/ExperimentalUrlRequest.java",
@@ -383,7 +382,9 @@
"--input_file " +
"java/lang/Runtime.class " +
"--javap " +
- "$$(find out/.path -name javap)",
+ "$$(find out/.path -name javap) " +
+ "--package_prefix " +
+ "android.net.http.internal",
out: [
"base/android_runtime_jni_headers/Runnable_jni.h",
"base/android_runtime_jni_headers/Runtime_jni.h",
@@ -1275,7 +1276,9 @@
"--input_file " +
"$(location base/android/java/src/org/chromium/base/task/PostTask.java) " +
"--input_file " +
- "$(location base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java)",
+ "$(location base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java) " +
+ "--package_prefix " +
+ "android.net.http.internal",
out: [
"base/base_jni_headers/ApkAssets_jni.h",
"base/base_jni_headers/ApplicationStatus_jni.h",
@@ -2507,7 +2510,9 @@
"--input_file " +
"$(location components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java) " +
"--input_file " +
- "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java)",
+ "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java) " +
+ "--package_prefix " +
+ "android.net.http.internal",
out: [
"components/cronet/android/cronet_jni_headers/CronetBidirectionalStream_jni.h",
"components/cronet/android/cronet_jni_headers/CronetLibraryLoader_jni.h",
@@ -2714,16 +2719,9 @@
"components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
"components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
"components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
- "components/cronet/android/java/src/org/chromium/net/impl/InputStreamChannel.java",
- "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngine.java",
- "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngineBuilderImpl.java",
- "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetProvider.java",
"components/cronet/android/java/src/org/chromium/net/impl/JavaUploadDataSinkBase.java",
- "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequest.java",
"components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequestUtils.java",
"components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java",
- "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderWithLibraryLoaderImpl.java",
- "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetProvider.java",
"components/cronet/android/java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
"components/cronet/android/java/src/org/chromium/net/impl/NoOpLogger.java",
"components/cronet/android/java/src/org/chromium/net/impl/Preconditions.java",
@@ -2783,6 +2781,8 @@
"--header-path " +
"$(genDir)/components/cronet/android/cronet_jni_registration.h " +
"--manual_jni_registration " +
+ "--package_prefix " +
+ "android.net.http.internal " +
";sed -i -e 's/OUT_SOONG_.TEMP_SBOX_.*_OUT/GEN/g' " +
"$(genDir)/components/cronet/android/cronet_jni_registration.h",
out: [
@@ -2988,16 +2988,9 @@
"components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
"components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
"components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
- "components/cronet/android/java/src/org/chromium/net/impl/InputStreamChannel.java",
- "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngine.java",
- "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngineBuilderImpl.java",
- "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetProvider.java",
"components/cronet/android/java/src/org/chromium/net/impl/JavaUploadDataSinkBase.java",
- "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequest.java",
"components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequestUtils.java",
"components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java",
- "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderWithLibraryLoaderImpl.java",
- "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetProvider.java",
"components/cronet/android/java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
"components/cronet/android/java/src/org/chromium/net/impl/NoOpLogger.java",
"components/cronet/android/java/src/org/chromium/net/impl/Preconditions.java",
@@ -3057,6 +3050,8 @@
"--header-path " +
"$(genDir)/components/cronet/android/cronet_jni_registration.h " +
"--manual_jni_registration " +
+ "--package_prefix " +
+ "android.net.http.internal " +
";sed -i -e 's/OUT_SOONG_.TEMP_SBOX_.*_OUT/GEN/g' " +
"$(genDir)/components/cronet/android/cronet_jni_registration.h",
out: [
@@ -3895,7 +3890,9 @@
"--output_name " +
"PrefService_jni.h " +
"--input_file " +
- "$(location components/prefs/android/java/src/org/chromium/components/prefs/PrefService.java)",
+ "$(location components/prefs/android/java/src/org/chromium/components/prefs/PrefService.java) " +
+ "--package_prefix " +
+ "android.net.http.internal",
out: [
"components/prefs/android/jni_headers/PrefService_jni.h",
],
@@ -4368,16 +4365,9 @@
"components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
"components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
"components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
- "components/cronet/android/java/src/org/chromium/net/impl/InputStreamChannel.java",
- "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngine.java",
- "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngineBuilderImpl.java",
- "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetProvider.java",
"components/cronet/android/java/src/org/chromium/net/impl/JavaUploadDataSinkBase.java",
- "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequest.java",
"components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequestUtils.java",
"components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java",
- "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderWithLibraryLoaderImpl.java",
- "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetProvider.java",
"components/cronet/android/java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
"components/cronet/android/java/src/org/chromium/net/impl/NoOpLogger.java",
"components/cronet/android/java/src/org/chromium/net/impl/Preconditions.java",
@@ -4421,8 +4411,10 @@
"net/android/java/src/org/chromium/net/X509Util.java",
"url/android/java/src/org/chromium/url/IDNStringUtil.java",
],
+ static_libs: [
+ "modules-utils-build_system",
+ ],
apex_available: [
- "//apex_available:platform",
"com.android.tethering",
],
libs: [
@@ -4453,6 +4445,7 @@
sdk_version: "module_current",
javacflags: [
"-Aorg.chromium.chrome.skipGenJni",
+ "-Apackage_prefix=android.net.http.internal",
],
}
@@ -4666,32 +4659,6 @@
],
}
-// GN: //net/data/ssl/chrome_root_store:gen_root_store_inc
-cc_genrule {
- name: "cronet_aml_net_data_ssl_chrome_root_store_gen_root_store_inc",
- cmd: "$(location build/gn_run_binary.py) clang_x64/root_store_tool " +
- "--root-store " +
- "../../net/data/ssl/chrome_root_store/root_store.textproto " +
- "--certs " +
- "../../net/data/ssl/chrome_root_store/root_store.certs " +
- "--write-cpp-root-store " +
- "gen/net/data/ssl/chrome_root_store/chrome-root-store-inc.cc " +
- "--write-cpp-ev-roots " +
- "gen/net/data/ssl/chrome_root_store/chrome-ev-roots-inc.cc",
- out: [
- "net/data/ssl/chrome_root_store/chrome-ev-roots-inc.cc",
- "net/data/ssl/chrome_root_store/chrome-root-store-inc.cc",
- ],
- tool_files: [
- "build/gn_run_binary.py",
- "net/data/ssl/chrome_root_store/root_store.certs",
- "net/data/ssl/chrome_root_store/root_store.textproto",
- ],
- apex_available: [
- "com.android.tethering",
- ],
-}
-
// GN: //net/dns:dns
cc_object {
name: "cronet_aml_net_dns_dns",
@@ -5884,7 +5851,9 @@
"--input_file " +
"$(location net/android/java/src/org/chromium/net/ProxyChangeListener.java) " +
"--input_file " +
- "$(location net/android/java/src/org/chromium/net/X509Util.java)",
+ "$(location net/android/java/src/org/chromium/net/X509Util.java) " +
+ "--package_prefix " +
+ "android.net.http.internal",
out: [
"net/net_jni_headers/AndroidCertVerifyResult_jni.h",
"net/net_jni_headers/AndroidKeyStore_jni.h",
@@ -10886,7 +10855,9 @@
"--input_file " +
"$(location url/android/java/src/org/chromium/url/IDNStringUtil.java) " +
"--input_file " +
- "$(location url/android/java/src/org/chromium/url/Origin.java)",
+ "$(location url/android/java/src/org/chromium/url/Origin.java) " +
+ "--package_prefix " +
+ "android.net.http.internal",
out: [
"url/url_jni_headers/IDNStringUtil_jni.h",
"url/url_jni_headers/Origin_jni.h",
diff --git a/tools/gn2bp/desc_arm.json b/tools/gn2bp/desc_arm.json
index d3e9ecd..dcfcd63 100644
--- a/tools/gn2bp/desc_arm.json
+++ b/tools/gn2bp/desc_arm.json
Binary files differ
diff --git a/tools/gn2bp/desc_arm64.json b/tools/gn2bp/desc_arm64.json
index 980a7a1..6a26578 100644
--- a/tools/gn2bp/desc_arm64.json
+++ b/tools/gn2bp/desc_arm64.json
Binary files differ
diff --git a/tools/gn2bp/desc_x64.json b/tools/gn2bp/desc_x64.json
index d0d9954..f69f50f 100644
--- a/tools/gn2bp/desc_x64.json
+++ b/tools/gn2bp/desc_x64.json
Binary files differ
diff --git a/tools/gn2bp/desc_x86.json b/tools/gn2bp/desc_x86.json
index 6d02af7..4f30229 100644
--- a/tools/gn2bp/desc_x86.json
+++ b/tools/gn2bp/desc_x86.json
Binary files differ
diff --git a/tools/gn2bp/gen_android_bp b/tools/gn2bp/gen_android_bp
index c750c2a..27c3b11 100755
--- a/tools/gn2bp/gen_android_bp
+++ b/tools/gn2bp/gen_android_bp
@@ -190,6 +190,8 @@
builtin_deps = {
'//buildtools/third_party/libunwind:libunwind':
lambda m, a: None, # disable libunwind
+ '//net/data/ssl/chrome_root_store:gen_root_store_inc':
+ lambda m, a: None,
'//net/tools/root_store_tool:root_store_tool':
lambda m, a: None,
'//third_party/zlib:zlib':
@@ -960,6 +962,7 @@
self._delete_value_arg('--prev_output_dir', False)
self._update_list_arg('--input_file', self._sanitize_filepath)
self._update_list_arg('--input_file', self._add_location_tag_to_filepath)
+ self._append_arg('--package_prefix', 'android.net.http.internal')
super()._sanitize_args()
def _sanitize_outputs(self):
@@ -990,6 +993,7 @@
# update_jni_registration_module removes them from the srcs of the module
# It might be better to remove sources by '--sources-exclusions'
self._delete_value_arg('--sources-exclusions')
+ self._append_arg('--package_prefix', 'android.net.http.internal')
super()._sanitize_args()
def get_cmd(self):
@@ -1245,8 +1249,7 @@
# aosp / soong builds and b) the include directory should already be
# configured via library dependency.
module.local_include_dirs.update([gn_utils.label_to_path(d)
- for d in include_dirs
- if not re.match('^//out/.*', d)])
+ for d in include_dirs if not d.startswith('//out')])
# Remove prohibited include directories
module.local_include_dirs = [d for d in module.local_include_dirs
if d not in local_include_dirs_denylist]
@@ -1308,17 +1311,13 @@
blueprint.add_module(module)
module.init_rc = target_initrc.get(target.name, [])
- module.srcs.update(
- gn_utils.label_to_path(src)
- for src in target.sources
- if is_supported_source_file(src) and not src.startswith("//out/test"))
+ module.srcs.update(gn_utils.label_to_path(src)
+ for src in target.sources if is_supported_source_file(src))
# Add arch-specific properties
for arch_name, arch in target.arch.items():
- module.target[arch_name].srcs.update(
- gn_utils.label_to_path(src)
- for src in arch.sources
- if is_supported_source_file(src) and not src.startswith("//out/test"))
+ module.target[arch_name].srcs.update(gn_utils.label_to_path(src)
+ for src in arch.sources if is_supported_source_file(src))
module.rtti = target.rtti
@@ -1527,6 +1526,9 @@
"framework-wifi.stubs.module_lib",
"framework-mediaprovider.stubs.module_lib",
}
+ module.static_libs = {
+ "modules-utils-build_system",
+ }
module.aidl["include_dirs"] = {"frameworks/base/core/java/"}
module.aidl["local_include_dirs"] = {"base/android/java/src/"}
module.sdk_version = "module_current"
@@ -1537,8 +1539,7 @@
# would be less likely to conflict with upstream changes if the revert is not
# accepted.
module.javacflags.add("-Aorg.chromium.chrome.skipGenJni")
- # TODO: remove following workaround required to make this module visible to make (b/203203405)
- module.apex_available.add("//apex_available:platform")
+ module.javacflags.add("-Apackage_prefix=android.net.http.internal")
for dep in get_non_api_java_actions(gn):
target = gn.get_target(dep)
if target.script == '//build/android/gyp/gcc_preprocess.py':
diff --git a/tools/gn2bp/gen_desc_json.sh b/tools/gn2bp/gen_desc_json.sh
new file mode 100755
index 0000000..510b967
--- /dev/null
+++ b/tools/gn2bp/gen_desc_json.sh
@@ -0,0 +1,54 @@
+#!/bin/bash
+set -x
+
+# Run this script inside a full chromium checkout.
+# TODO: add support for applying local patches.
+
+OUT_PATH="out/cronet"
+
+#######################################
+# Generate desc.json for a specified architecture.
+# Globals:
+# OUT_PATH
+# Arguments:
+# target_cpu, string
+#######################################
+function gn_desc() {
+ local -a gn_args=(
+ "target_os = \"android\""
+ "enable_websockets = false"
+ "disable_file_support = true"
+ "disable_brotli_filter = false"
+ "is_component_build = false"
+ "use_crash_key_stubs = true"
+ "use_partition_alloc = false"
+ "include_transport_security_state_preload_list = false"
+ "use_platform_icu_alternatives = true"
+ "default_min_sdk_version = 19"
+ "use_errorprone_java_compiler = true"
+ "enable_reporting = true"
+ "use_hashed_jni_names = true"
+ "treat_warnings_as_errors = false"
+ "enable_base_tracing = false"
+ )
+ gn_args+=("target_cpu = \"${1}\"")
+
+ # Only set arm_use_neon on arm architectures to prevent warning from being
+ # written to json output.
+ if [[ "$1" =~ ^arm ]]; then
+ gn_args+=("arm_use_neon = false")
+ fi
+
+ # Configure gn args.
+ gn gen "${OUT_PATH}" --args="${gn_args[*]}"
+
+ # Generate desc.json.
+ local -r out_file="desc_${1}.json"
+ gn desc "${OUT_PATH}" --format=json --all-toolchains "//*" > "${out_file}"
+}
+
+gn_desc x86
+gn_desc x64
+gn_desc arm
+gn_desc arm64
+
diff --git a/tools/gn2bp/gn_utils.py b/tools/gn2bp/gn_utils.py
index e5d2bf4..3d709e5 100644
--- a/tools/gn2bp/gn_utils.py
+++ b/tools/gn2bp/gn_utils.py
@@ -78,7 +78,7 @@
return name
def _is_java_source(src):
- return os.path.splitext(src)[1] == '.java' and not src.startswith("//out/test/gen/")
+ return os.path.splitext(src)[1] == '.java' and not src.startswith("//out/")
def is_java_action(script, outputs):
return (script != "" and script not in JAVA_BANNED_SCRIPTS) and any(