summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2021-04-05 16:32:09 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2021-04-05 16:32:09 +0000
commite8f470df66c96404ef0dd70bc87b2c26782fef9b (patch)
tree9821a8992c30a86dda7bace4d78bcc1366072dec
parent235bacb28d720bb82652649672fc33073eef1a91 (diff)
parentcf257bb21799f54ba11d63c44905be671596bdc7 (diff)
downloadConnectivity-e8f470df66c96404ef0dd70bc87b2c26782fef9b.tar.gz
Snap for 7259092 from cf257bb21799f54ba11d63c44905be671596bdc7 to mainline-media-release
Change-Id: Ieaa39f56c4859e03850b477b76294268b17bf0d8
-rw-r--r--Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java12
-rw-r--r--Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java62
-rw-r--r--Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java14
-rw-r--r--Tethering/bpf_progs/Android.bp4
-rw-r--r--Tethering/bpf_progs/offload.c175
-rw-r--r--Tethering/jni/com_android_networkstack_tethering_BpfUtils.cpp350
-rw-r--r--Tethering/jni/onload.cpp3
-rw-r--r--Tethering/src/android/net/ip/IpServer.java2
-rw-r--r--Tethering/src/android/net/ip/RouterAdvertisementDaemon.java4
-rw-r--r--Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java97
-rw-r--r--Tethering/src/com/android/networkstack/tethering/BpfUtils.java144
-rw-r--r--Tethering/src/com/android/networkstack/tethering/Tethering.java7
-rw-r--r--Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java53
-rw-r--r--Tethering/tests/mts/Android.bp2
-rw-r--r--Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java305
-rw-r--r--Tethering/tests/unit/src/android/net/ip/IpServerTest.java52
-rw-r--r--Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java100
-rw-r--r--Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java120
-rw-r--r--Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java247
-rw-r--r--Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java108
-rw-r--r--Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java175
-rw-r--r--tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java19
-rw-r--r--tests/cts/hostside/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java20
-rw-r--r--tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java127
-rw-r--r--tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java6
-rw-r--r--tests/cts/net/Android.bp5
-rw-r--r--tests/cts/net/native/dns/Android.bp3
-rw-r--r--tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java92
-rw-r--r--tests/cts/net/src/android/net/cts/NetworkAgentTest.kt8
-rw-r--r--tests/cts/net/src/android/net/cts/NetworkRequestTest.java36
-rw-r--r--tests/cts/net/src/android/net/cts/VpnServiceTest.java6
-rw-r--r--tests/cts/tethering/Android.bp2
32 files changed, 2031 insertions, 329 deletions
diff --git a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
index 4e615a197e..f27c831689 100644
--- a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
@@ -159,6 +159,18 @@ public class BpfCoordinatorShimImpl
}
@Override
+ public boolean attachProgram(String iface, boolean downstream) {
+ /* no op */
+ return true;
+ }
+
+ @Override
+ public boolean detachProgram(String iface) {
+ /* no op */
+ return true;
+ }
+
+ @Override
public String toString() {
return "Netd used";
}
diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
index 4dc1c51f68..4f7fe65a6a 100644
--- a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
@@ -31,6 +31,7 @@ import androidx.annotation.Nullable;
import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
import com.android.networkstack.tethering.BpfMap;
+import com.android.networkstack.tethering.BpfUtils;
import com.android.networkstack.tethering.Tether4Key;
import com.android.networkstack.tethering.Tether4Value;
import com.android.networkstack.tethering.Tether6Value;
@@ -42,6 +43,7 @@ import com.android.networkstack.tethering.TetherStatsValue;
import com.android.networkstack.tethering.TetherUpstream6Key;
import java.io.FileDescriptor;
+import java.io.IOException;
/**
* Bpf coordinator class for API shims.
@@ -84,12 +86,46 @@ public class BpfCoordinatorShimImpl
public BpfCoordinatorShimImpl(@NonNull final Dependencies deps) {
mLog = deps.getSharedLog().forSubComponent(TAG);
+
mBpfDownstream4Map = deps.getBpfDownstream4Map();
mBpfUpstream4Map = deps.getBpfUpstream4Map();
mBpfDownstream6Map = deps.getBpfDownstream6Map();
mBpfUpstream6Map = deps.getBpfUpstream6Map();
mBpfStatsMap = deps.getBpfStatsMap();
mBpfLimitMap = deps.getBpfLimitMap();
+
+ // Clear the stubs of the maps for handling the system service crash if any.
+ // Doesn't throw the exception and clear the stubs as many as possible.
+ try {
+ if (mBpfDownstream4Map != null) mBpfDownstream4Map.clear();
+ } catch (ErrnoException e) {
+ mLog.e("Could not clear mBpfDownstream4Map: " + e);
+ }
+ try {
+ if (mBpfUpstream4Map != null) mBpfUpstream4Map.clear();
+ } catch (ErrnoException e) {
+ mLog.e("Could not clear mBpfUpstream4Map: " + e);
+ }
+ try {
+ if (mBpfDownstream6Map != null) mBpfDownstream6Map.clear();
+ } catch (ErrnoException e) {
+ mLog.e("Could not clear mBpfDownstream6Map: " + e);
+ }
+ try {
+ if (mBpfUpstream6Map != null) mBpfUpstream6Map.clear();
+ } catch (ErrnoException e) {
+ mLog.e("Could not clear mBpfUpstream6Map: " + e);
+ }
+ try {
+ if (mBpfStatsMap != null) mBpfStatsMap.clear();
+ } catch (ErrnoException e) {
+ mLog.e("Could not clear mBpfStatsMap: " + e);
+ }
+ try {
+ if (mBpfLimitMap != null) mBpfLimitMap.clear();
+ } catch (ErrnoException e) {
+ mLog.e("Could not clear mBpfLimitMap: " + e);
+ }
}
@Override
@@ -324,6 +360,32 @@ public class BpfCoordinatorShimImpl
return true;
}
+ @Override
+ public boolean attachProgram(String iface, boolean downstream) {
+ if (!isInitialized()) return false;
+
+ try {
+ BpfUtils.attachProgram(iface, downstream);
+ } catch (IOException e) {
+ mLog.e("Could not attach program: " + e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean detachProgram(String iface) {
+ if (!isInitialized()) return false;
+
+ try {
+ BpfUtils.detachProgram(iface);
+ } catch (IOException e) {
+ mLog.e("Could not detach program: " + e);
+ return false;
+ }
+ return true;
+ }
+
private String mapStatus(BpfMap m, String name) {
return name + "{" + (m != null ? "OK" : "ERROR") + "}";
}
diff --git a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
index c61c44902f..b7b4c47cc3 100644
--- a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
+++ b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
@@ -143,5 +143,19 @@ public abstract class BpfCoordinatorShim {
* Deletes a tethering IPv4 offload rule from the appropriate BPF map.
*/
public abstract boolean tetherOffloadRuleRemove(boolean downstream, @NonNull Tether4Key key);
+
+ /**
+ * Attach BPF program.
+ *
+ * TODO: consider using InterfaceParams to replace interface name.
+ */
+ public abstract boolean attachProgram(@NonNull String iface, boolean downstream);
+
+ /**
+ * Detach BPF program.
+ *
+ * TODO: consider using InterfaceParams to replace interface name.
+ */
+ public abstract boolean detachProgram(@NonNull String iface);
}
diff --git a/Tethering/bpf_progs/Android.bp b/Tethering/bpf_progs/Android.bp
index 2b10f89761..289d75d0b0 100644
--- a/Tethering/bpf_progs/Android.bp
+++ b/Tethering/bpf_progs/Android.bp
@@ -51,8 +51,6 @@ bpf {
include_dirs: [
// TODO: get rid of system/netd.
"system/netd/bpf_progs", // for bpf_net_helpers.h
- "system/netd/libnetdbpf/include", // for bpf_shared.h
- "system/netd/libnetdutils/include", // for UidConstants.h
],
}
@@ -66,7 +64,5 @@ bpf {
include_dirs: [
// TODO: get rid of system/netd.
"system/netd/bpf_progs", // for bpf_net_helpers.h
- "system/netd/libnetdbpf/include", // for bpf_shared.h
- "system/netd/libnetdutils/include", // for UidConstants.h
],
}
diff --git a/Tethering/bpf_progs/offload.c b/Tethering/bpf_progs/offload.c
index bfa1e3a192..5f29d4f60a 100644
--- a/Tethering/bpf_progs/offload.c
+++ b/Tethering/bpf_progs/offload.c
@@ -72,11 +72,11 @@
DEFINE_BPF_MAP_GRW(tether_error_map, ARRAY, uint32_t, uint32_t, BPF_TETHER_ERR__MAX,
AID_NETWORK_STACK)
-#define COUNT_AND_RETURN(counter, ret) do { \
+#define COUNT_AND_RETURN(counter, ret) do { \
uint32_t code = BPF_TETHER_ERR_ ## counter; \
uint32_t *count = bpf_tether_error_map_lookup_elem(&code); \
- if (count) __sync_fetch_and_add(count, 1); \
- return ret; \
+ if (count) __sync_fetch_and_add(count, 1); \
+ return ret; \
} while(0)
#define TC_DROP(counter) COUNT_AND_RETURN(counter, TC_ACT_SHOT)
@@ -600,13 +600,7 @@ static inline __always_inline int do_forward4(struct __sk_buff* skb, const bool
return bpf_redirect(v->oif, 0 /* this is effectively BPF_F_EGRESS */);
}
-// Full featured (required) implementations for 5.8+ kernels
-
-DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_ether$5_8", AID_ROOT, AID_NETWORK_STACK,
- sched_cls_tether_downstream4_ether_5_8, KVER(5, 8, 0))
-(struct __sk_buff* skb) {
- return do_forward4(skb, /* is_ethernet */ true, /* downstream */ true, /* updatetime */ true);
-}
+// Full featured (required) implementations for 5.8+ kernels (these are S+ by definition)
DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_rawip$5_8", AID_ROOT, AID_NETWORK_STACK,
sched_cls_tether_downstream4_rawip_5_8, KVER(5, 8, 0))
@@ -614,28 +608,27 @@ DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_rawip$5_8", AID_ROOT, AID_NETW
return do_forward4(skb, /* is_ethernet */ false, /* downstream */ true, /* updatetime */ true);
}
-DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_ether$5_8", AID_ROOT, AID_NETWORK_STACK,
- sched_cls_tether_upstream4_ether_5_8, KVER(5, 8, 0))
-(struct __sk_buff* skb) {
- return do_forward4(skb, /* is_ethernet */ true, /* downstream */ false, /* updatetime */ true);
-}
-
DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_rawip$5_8", AID_ROOT, AID_NETWORK_STACK,
sched_cls_tether_upstream4_rawip_5_8, KVER(5, 8, 0))
(struct __sk_buff* skb) {
return do_forward4(skb, /* is_ethernet */ false, /* downstream */ false, /* updatetime */ true);
}
-// Full featured (optional) implementations for [4.14..5.8) kernels
-
-DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$opt",
- AID_ROOT, AID_NETWORK_STACK,
- sched_cls_tether_downstream4_ether_opt,
- KVER(4, 14, 0), KVER(5, 8, 0))
+DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_ether$5_8", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_downstream4_ether_5_8, KVER(5, 8, 0))
(struct __sk_buff* skb) {
return do_forward4(skb, /* is_ethernet */ true, /* downstream */ true, /* updatetime */ true);
}
+DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_ether$5_8", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_upstream4_ether_5_8, KVER(5, 8, 0))
+(struct __sk_buff* skb) {
+ return do_forward4(skb, /* is_ethernet */ true, /* downstream */ false, /* updatetime */ true);
+}
+
+// Full featured (optional) implementations for 4.14-S, 4.19-S & 5.4-S kernels
+// (optional, because we need to be able to fallback for 4.14/4.19/5.4 pre-S kernels)
+
DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$opt",
AID_ROOT, AID_NETWORK_STACK,
sched_cls_tether_downstream4_rawip_opt,
@@ -644,24 +637,33 @@ DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$opt",
return do_forward4(skb, /* is_ethernet */ false, /* downstream */ true, /* updatetime */ true);
}
-DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$opt",
+DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$opt",
AID_ROOT, AID_NETWORK_STACK,
- sched_cls_tether_upstream4_ether_opt,
+ sched_cls_tether_upstream4_rawip_opt,
KVER(4, 14, 0), KVER(5, 8, 0))
(struct __sk_buff* skb) {
- return do_forward4(skb, /* is_ethernet */ true, /* downstream */ false, /* updatetime */ true);
+ return do_forward4(skb, /* is_ethernet */ false, /* downstream */ false, /* updatetime */ true);
}
-DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$opt",
+DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$opt",
AID_ROOT, AID_NETWORK_STACK,
- sched_cls_tether_upstream4_rawip_opt,
+ sched_cls_tether_downstream4_ether_opt,
KVER(4, 14, 0), KVER(5, 8, 0))
(struct __sk_buff* skb) {
- return do_forward4(skb, /* is_ethernet */ false, /* downstream */ false, /* updatetime */ true);
+ return do_forward4(skb, /* is_ethernet */ true, /* downstream */ true, /* updatetime */ true);
+}
+
+DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$opt",
+ AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_upstream4_ether_opt,
+ KVER(4, 14, 0), KVER(5, 8, 0))
+(struct __sk_buff* skb) {
+ return do_forward4(skb, /* is_ethernet */ true, /* downstream */ false, /* updatetime */ true);
}
// Partial (TCP-only: will not update 'last_used' field) implementations for 4.14+ kernels.
-// These will be loaded only if the above optional ones failed (loading of *these* must succeed).
+// These will be loaded only if the above optional ones failed (loading of *these* must succeed
+// for 5.4+, since that is always an R patched kernel).
//
// [Note: as a result TCP connections will not have their conntrack timeout refreshed, however,
// since /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established defaults to 432000 (seconds),
@@ -671,52 +673,79 @@ DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$opt",
// which enforces and documents the required kernel cherrypicks will make it pretty unlikely that
// many devices upgrading to S will end up relying on these fallback programs.
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$4_14", AID_ROOT, AID_NETWORK_STACK,
- sched_cls_tether_downstream4_ether_4_14, KVER(4, 14, 0), KVER(5, 8, 0))
+// RAWIP: Required for 5.4-R kernels -- which always support bpf_skb_change_head().
+
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$5_4", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_downstream4_rawip_5_4, KVER(5, 4, 0), KVER(5, 8, 0))
(struct __sk_buff* skb) {
- return do_forward4(skb, /* is_ethernet */ true, /* downstream */ true, /* updatetime */ false);
+ return do_forward4(skb, /* is_ethernet */ false, /* downstream */ true, /* updatetime */ false);
}
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$4_14", AID_ROOT, AID_NETWORK_STACK,
- sched_cls_tether_downstream4_rawip_4_14, KVER(4, 14, 0), KVER(5, 8, 0))
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$5_4", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_upstream4_rawip_5_4, KVER(5, 4, 0), KVER(5, 8, 0))
+(struct __sk_buff* skb) {
+ return do_forward4(skb, /* is_ethernet */ false, /* downstream */ false, /* updatetime */ false);
+}
+
+// RAWIP: Optional for 4.14/4.19 (R) kernels -- which support bpf_skb_change_head().
+// [Note: fallback for 4.14/4.19 (P/Q) kernels is below in stub section]
+
+DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$4_14",
+ AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_downstream4_rawip_4_14,
+ KVER(4, 14, 0), KVER(5, 4, 0))
(struct __sk_buff* skb) {
return do_forward4(skb, /* is_ethernet */ false, /* downstream */ true, /* updatetime */ false);
}
+DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$4_14",
+ AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_upstream4_rawip_4_14,
+ KVER(4, 14, 0), KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+ return do_forward4(skb, /* is_ethernet */ false, /* downstream */ false, /* updatetime */ false);
+}
+
+// ETHER: Required for 4.14-Q/R, 4.19-Q/R & 5.4-R kernels.
+
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$4_14", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_downstream4_ether_4_14, KVER(4, 14, 0), KVER(5, 8, 0))
+(struct __sk_buff* skb) {
+ return do_forward4(skb, /* is_ethernet */ true, /* downstream */ true, /* updatetime */ false);
+}
+
DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$4_14", AID_ROOT, AID_NETWORK_STACK,
sched_cls_tether_upstream4_ether_4_14, KVER(4, 14, 0), KVER(5, 8, 0))
(struct __sk_buff* skb) {
return do_forward4(skb, /* is_ethernet */ true, /* downstream */ false, /* updatetime */ false);
}
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$4_14", AID_ROOT, AID_NETWORK_STACK,
- sched_cls_tether_upstream4_rawip_4_14, KVER(4, 14, 0), KVER(5, 8, 0))
-(struct __sk_buff* skb) {
- return do_forward4(skb, /* is_ethernet */ false, /* downstream */ false, /* updatetime */ false);
-}
+// Placeholder (no-op) implementations for older Q kernels
-// Placeholder (no-op) implementations for older pre-4.14 kernels
+// RAWIP: 4.9-P/Q, 4.14-P/Q & 4.19-Q kernels -- without bpf_skb_change_head() for tc programs
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$stub", AID_ROOT, AID_NETWORK_STACK,
- sched_cls_tether_downstream4_ether_stub, KVER_NONE, KVER(4, 14, 0))
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$stub", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_downstream4_rawip_stub, KVER_NONE, KVER(5, 4, 0))
(struct __sk_buff* skb) {
return TC_ACT_OK;
}
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$stub", AID_ROOT, AID_NETWORK_STACK,
- sched_cls_tether_downstream4_rawip_stub, KVER_NONE, KVER(4, 14, 0))
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$stub", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_upstream4_rawip_stub, KVER_NONE, KVER(5, 4, 0))
(struct __sk_buff* skb) {
return TC_ACT_OK;
}
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$stub", AID_ROOT, AID_NETWORK_STACK,
- sched_cls_tether_upstream4_ether_stub, KVER_NONE, KVER(4, 14, 0))
+// ETHER: 4.9-P/Q kernel
+
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$stub", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_downstream4_ether_stub, KVER_NONE, KVER(4, 14, 0))
(struct __sk_buff* skb) {
return TC_ACT_OK;
}
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$stub", AID_ROOT, AID_NETWORK_STACK,
- sched_cls_tether_upstream4_rawip_stub, KVER_NONE, KVER(4, 14, 0))
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$stub", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_upstream4_ether_stub, KVER_NONE, KVER(4, 14, 0))
(struct __sk_buff* skb) {
return TC_ACT_OK;
}
@@ -726,27 +755,69 @@ DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$stub", AID_ROOT, AID
DEFINE_BPF_MAP_GRW(tether_xdp_devmap, DEVMAP_HASH, uint32_t, uint32_t, 64,
AID_NETWORK_STACK)
+static inline __always_inline int do_xdp_forward6(struct xdp_md *ctx, const bool is_ethernet,
+ const bool downstream) {
+ return XDP_PASS;
+}
+
+static inline __always_inline int do_xdp_forward4(struct xdp_md *ctx, const bool is_ethernet,
+ const bool downstream) {
+ return XDP_PASS;
+}
+
+static inline __always_inline int do_xdp_forward_ether(struct xdp_md *ctx, const bool downstream) {
+ const void* data = (void*)(long)ctx->data;
+ const void* data_end = (void*)(long)ctx->data_end;
+ const struct ethhdr* eth = data;
+
+ // Make sure we actually have an ethernet header
+ if ((void*)(eth + 1) > data_end) return XDP_PASS;
+
+ if (eth->h_proto == htons(ETH_P_IPV6))
+ return do_xdp_forward6(ctx, /* is_ethernet */ true, downstream);
+ if (eth->h_proto == htons(ETH_P_IP))
+ return do_xdp_forward4(ctx, /* is_ethernet */ true, downstream);
+
+ // Anything else we don't know how to handle...
+ return XDP_PASS;
+}
+
+static inline __always_inline int do_xdp_forward_rawip(struct xdp_md *ctx, const bool downstream) {
+ const void* data = (void*)(long)ctx->data;
+ const void* data_end = (void*)(long)ctx->data_end;
+
+ // The top nibble of both IPv4 and IPv6 headers is the IP version.
+ if (data_end - data < 1) return XDP_PASS;
+ const uint8_t v = (*(uint8_t*)data) >> 4;
+
+ if (v == 6) return do_xdp_forward6(ctx, /* is_ethernet */ false, downstream);
+ if (v == 4) return do_xdp_forward4(ctx, /* is_ethernet */ false, downstream);
+
+ // Anything else we don't know how to handle...
+ return XDP_PASS;
+}
+
#define DEFINE_XDP_PROG(str, func) \
DEFINE_BPF_PROG_KVER(str, AID_ROOT, AID_NETWORK_STACK, func, KVER(5, 9, 0))(struct xdp_md *ctx)
DEFINE_XDP_PROG("xdp/tether_downstream_ether",
xdp_tether_downstream_ether) {
- return XDP_PASS;
+ return do_xdp_forward_ether(ctx, /* downstream */ true);
}
DEFINE_XDP_PROG("xdp/tether_downstream_rawip",
xdp_tether_downstream_rawip) {
- return XDP_PASS;
+ return do_xdp_forward_rawip(ctx, /* downstream */ true);
}
DEFINE_XDP_PROG("xdp/tether_upstream_ether",
xdp_tether_upstream_ether) {
- return XDP_PASS;
+ return do_xdp_forward_ether(ctx, /* downstream */ false);
}
DEFINE_XDP_PROG("xdp/tether_upstream_rawip",
xdp_tether_upstream_rawip) {
- return XDP_PASS;
+ return do_xdp_forward_rawip(ctx, /* downstream */ false);
}
LICENSE("Apache 2.0");
diff --git a/Tethering/jni/com_android_networkstack_tethering_BpfUtils.cpp b/Tethering/jni/com_android_networkstack_tethering_BpfUtils.cpp
new file mode 100644
index 0000000000..308dfb9c98
--- /dev/null
+++ b/Tethering/jni/com_android_networkstack_tethering_BpfUtils.cpp
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include <arpa/inet.h>
+#include <jni.h>
+#include <linux/if_arp.h>
+#include <linux/if_ether.h>
+#include <linux/netlink.h>
+#include <linux/pkt_cls.h>
+#include <linux/pkt_sched.h>
+#include <linux/rtnetlink.h>
+#include <nativehelper/JNIHelp.h>
+#include <net/if.h>
+#include <stdio.h>
+#include <sys/socket.h>
+
+// TODO: use unique_fd.
+#define BPF_FD_JUST_USE_INT
+#include "BpfSyscallWrappers.h"
+#include "bpf_tethering.h"
+#include "nativehelper/scoped_utf_chars.h"
+
+// The maximum length of TCA_BPF_NAME. Sync from net/sched/cls_bpf.c.
+#define CLS_BPF_NAME_LEN 256
+
+namespace android {
+// Sync from system/netd/server/NetlinkCommands.h
+const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK;
+const sockaddr_nl KERNEL_NLADDR = {AF_NETLINK, 0, 0, 0};
+
+// TODO: move to frameworks/libs/net/common/native for sharing with
+// system/netd/server/OffloadUtils.{c, h}.
+static void sendAndProcessNetlinkResponse(JNIEnv* env, const void* req, int len) {
+ int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); // TODO: use unique_fd
+ if (fd == -1) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE): %s",
+ strerror(errno));
+ return;
+ }
+
+ static constexpr int on = 1;
+ if (setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &on, sizeof(on))) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, %d)", on);
+ close(fd);
+ return;
+ }
+
+ // this is needed to get valid strace netlink parsing, it allocates the pid
+ if (bind(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR))) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "bind(fd, {AF_NETLINK, 0, 0}): %s",
+ strerror(errno));
+ close(fd);
+ return;
+ }
+
+ // we do not want to receive messages from anyone besides the kernel
+ if (connect(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR))) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "connect(fd, {AF_NETLINK, 0, 0}): %s",
+ strerror(errno));
+ close(fd);
+ return;
+ }
+
+ int rv = send(fd, req, len, 0);
+
+ if (rv == -1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "send(fd, req, len, 0): %s",
+ strerror(errno));
+ close(fd);
+ return;
+ }
+
+ if (rv != len) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "send(fd, req, len, 0): %s",
+ strerror(EMSGSIZE));
+ close(fd);
+ return;
+ }
+
+ struct {
+ nlmsghdr h;
+ nlmsgerr e;
+ char buf[256];
+ } resp = {};
+
+ rv = recv(fd, &resp, sizeof(resp), MSG_TRUNC);
+
+ if (rv == -1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "recv() failed: %s", strerror(errno));
+ close(fd);
+ return;
+ }
+
+ if (rv < (int)NLMSG_SPACE(sizeof(struct nlmsgerr))) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "recv() returned short packet: %d", rv);
+ close(fd);
+ return;
+ }
+
+ if (resp.h.nlmsg_len != (unsigned)rv) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "recv() returned invalid header length: %d != %d", resp.h.nlmsg_len,
+ rv);
+ close(fd);
+ return;
+ }
+
+ if (resp.h.nlmsg_type != NLMSG_ERROR) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "recv() did not return NLMSG_ERROR message: %d", resp.h.nlmsg_type);
+ close(fd);
+ return;
+ }
+
+ if (resp.e.error) { // returns 0 on success
+ jniThrowExceptionFmt(env, "java/io/IOException", "NLMSG_ERROR message return error: %s",
+ strerror(-resp.e.error));
+ }
+ close(fd);
+ return;
+}
+
+static int hardwareAddressType(const char* interface) {
+ int fd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (fd < 0) return -errno;
+
+ struct ifreq ifr = {};
+ // We use strncpy() instead of strlcpy() since kernel has to be able
+ // to handle non-zero terminated junk passed in by userspace anyway,
+ // and this way too long interface names (more than IFNAMSIZ-1 = 15
+ // characters plus terminating NULL) will not get truncated to 15
+ // characters and zero-terminated and thus potentially erroneously
+ // match a truncated interface if one were to exist.
+ strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));
+
+ int rv;
+ if (ioctl(fd, SIOCGIFHWADDR, &ifr, sizeof(ifr))) {
+ rv = -errno;
+ } else {
+ rv = ifr.ifr_hwaddr.sa_family;
+ }
+
+ close(fd);
+ return rv;
+}
+
+static jboolean com_android_networkstack_tethering_BpfUtils_isEthernet(JNIEnv* env, jobject clazz,
+ jstring iface) {
+ ScopedUtfChars interface(env, iface);
+
+ int rv = hardwareAddressType(interface.c_str());
+ if (rv < 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "Get hardware address type of interface %s failed: %s",
+ interface.c_str(), strerror(-rv));
+ return false;
+ }
+
+ switch (rv) {
+ case ARPHRD_ETHER:
+ return true;
+ case ARPHRD_NONE:
+ case ARPHRD_RAWIP: // in Linux 4.14+ rmnet support was upstreamed and this is 519
+ case 530: // this is ARPHRD_RAWIP on some Android 4.9 kernels with rmnet
+ return false;
+ default:
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "Unknown hardware address type %s on interface %s", rv,
+ interface.c_str());
+ return false;
+ }
+}
+
+// tc filter add dev .. in/egress prio 1 protocol ipv6/ip bpf object-pinned /sys/fs/bpf/...
+// direct-action
+static void com_android_networkstack_tethering_BpfUtils_tcFilterAddDevBpf(
+ JNIEnv* env, jobject clazz, jint ifIndex, jboolean ingress, jshort prio, jshort proto,
+ jstring bpfProgPath) {
+ ScopedUtfChars pathname(env, bpfProgPath);
+
+ const int bpfFd = bpf::retrieveProgram(pathname.c_str());
+ if (bpfFd == -1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "retrieveProgram failed %s",
+ strerror(errno));
+ return;
+ }
+
+ struct {
+ nlmsghdr n;
+ tcmsg t;
+ struct {
+ nlattr attr;
+ // The maximum classifier name length is defined as IFNAMSIZ.
+ // See tcf_proto_ops in include/net/sch_generic.h.
+ char str[NLMSG_ALIGN(IFNAMSIZ)];
+ } kind;
+ struct {
+ nlattr attr;
+ struct {
+ nlattr attr;
+ __u32 u32;
+ } fd;
+ struct {
+ nlattr attr;
+ char str[NLMSG_ALIGN(CLS_BPF_NAME_LEN)];
+ } name;
+ struct {
+ nlattr attr;
+ __u32 u32;
+ } flags;
+ } options;
+ } req = {
+ .n =
+ {
+ .nlmsg_len = sizeof(req),
+ .nlmsg_type = RTM_NEWTFILTER,
+ .nlmsg_flags = NETLINK_REQUEST_FLAGS | NLM_F_EXCL | NLM_F_CREATE,
+ },
+ .t =
+ {
+ .tcm_family = AF_UNSPEC,
+ .tcm_ifindex = ifIndex,
+ .tcm_handle = TC_H_UNSPEC,
+ .tcm_parent = TC_H_MAKE(TC_H_CLSACT,
+ ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
+ .tcm_info = static_cast<__u32>((static_cast<uint16_t>(prio) << 16) |
+ htons(static_cast<uint16_t>(proto))),
+ },
+ .kind =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.kind),
+ .nla_type = TCA_KIND,
+ },
+ // Classifier name. See cls_bpf_ops in net/sched/cls_bpf.c.
+ .str = "bpf",
+ },
+ .options =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options),
+ .nla_type = NLA_F_NESTED | TCA_OPTIONS,
+ },
+ .fd =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options.fd),
+ .nla_type = TCA_BPF_FD,
+ },
+ .u32 = static_cast<__u32>(bpfFd),
+ },
+ .name =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options.name),
+ .nla_type = TCA_BPF_NAME,
+ },
+ // Visible via 'tc filter show', but
+ // is overwritten by strncpy below
+ .str = "placeholder",
+ },
+ .flags =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options.flags),
+ .nla_type = TCA_BPF_FLAGS,
+ },
+ .u32 = TCA_BPF_FLAG_ACT_DIRECT,
+ },
+ },
+ };
+
+ snprintf(req.options.name.str, sizeof(req.options.name.str), "%s:[*fsobj]",
+ basename(pathname.c_str()));
+
+ // The exception may be thrown from sendAndProcessNetlinkResponse. Close the file descriptor of
+ // BPF program before returning the function in any case.
+ sendAndProcessNetlinkResponse(env, &req, sizeof(req));
+ close(bpfFd);
+}
+
+// tc filter del dev .. in/egress prio .. protocol ..
+static void com_android_networkstack_tethering_BpfUtils_tcFilterDelDev(JNIEnv* env, jobject clazz,
+ jint ifIndex,
+ jboolean ingress,
+ jshort prio, jshort proto) {
+ const struct {
+ nlmsghdr n;
+ tcmsg t;
+ } req = {
+ .n =
+ {
+ .nlmsg_len = sizeof(req),
+ .nlmsg_type = RTM_DELTFILTER,
+ .nlmsg_flags = NETLINK_REQUEST_FLAGS,
+ },
+ .t =
+ {
+ .tcm_family = AF_UNSPEC,
+ .tcm_ifindex = ifIndex,
+ .tcm_handle = TC_H_UNSPEC,
+ .tcm_parent = TC_H_MAKE(TC_H_CLSACT,
+ ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
+ .tcm_info = static_cast<__u32>((static_cast<uint16_t>(prio) << 16) |
+ htons(static_cast<uint16_t>(proto))),
+ },
+ };
+
+ sendAndProcessNetlinkResponse(env, &req, sizeof(req));
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ {"isEthernet", "(Ljava/lang/String;)Z",
+ (void*)com_android_networkstack_tethering_BpfUtils_isEthernet},
+ {"tcFilterAddDevBpf", "(IZSSLjava/lang/String;)V",
+ (void*)com_android_networkstack_tethering_BpfUtils_tcFilterAddDevBpf},
+ {"tcFilterDelDev", "(IZSS)V",
+ (void*)com_android_networkstack_tethering_BpfUtils_tcFilterDelDev},
+};
+
+int register_com_android_networkstack_tethering_BpfUtils(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/networkstack/tethering/BpfUtils", gMethods,
+ NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/Tethering/jni/onload.cpp b/Tethering/jni/onload.cpp
index e31da60ef5..02e602d99e 100644
--- a/Tethering/jni/onload.cpp
+++ b/Tethering/jni/onload.cpp
@@ -25,6 +25,7 @@ namespace android {
int register_android_net_util_TetheringUtils(JNIEnv* env);
int register_com_android_networkstack_tethering_BpfMap(JNIEnv* env);
int register_com_android_networkstack_tethering_BpfCoordinator(JNIEnv* env);
+int register_com_android_networkstack_tethering_BpfUtils(JNIEnv* env);
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
JNIEnv *env;
@@ -39,6 +40,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
if (register_com_android_networkstack_tethering_BpfCoordinator(env) < 0) return JNI_ERR;
+ if (register_com_android_networkstack_tethering_BpfUtils(env) < 0) return JNI_ERR;
+
return JNI_VERSION_1_6;
}
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 194737af21..e5380e008d 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -1291,6 +1291,7 @@ public class IpServer extends StateMachine {
// Sometimes interfaces are gone before we get
// to remove their rules, which generates errors.
// Just do the best we can.
+ mBpfCoordinator.maybeDetachProgram(mIfaceName, upstreamIface);
try {
mNetd.ipfwdRemoveInterfaceForward(mIfaceName, upstreamIface);
} catch (RemoteException | ServiceSpecificException e) {
@@ -1334,6 +1335,7 @@ public class IpServer extends StateMachine {
mUpstreamIfaceSet = newUpstreamIfaceSet;
for (String ifname : added) {
+ mBpfCoordinator.maybeAttachProgram(mIfaceName, ifname);
try {
mNetd.tetherAddForward(mIfaceName, ifname);
mNetd.ipfwdAddInterfaceForward(mIfaceName, ifname);
diff --git a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
index 7c0b7cc751..afccd0a5d9 100644
--- a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
+++ b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
@@ -37,7 +37,7 @@ import android.system.StructTimeval;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.TrafficStatsConstants;
+import com.android.net.module.util.NetworkStackConstants;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -589,7 +589,7 @@ public class RouterAdvertisementDaemon {
final int send_timout_ms = 300;
final int oldTag = TrafficStats.getAndSetThreadStatsTag(
- TrafficStatsConstants.TAG_SYSTEM_NEIGHBOR);
+ NetworkStackConstants.TAG_SYSTEM_NEIGHBOR);
try {
mSocket = Os.socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
// Setting SNDTIMEO is purely for defensive purposes.
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 985328fe60..7fb73b4904 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -28,6 +28,8 @@ import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED
import static android.system.OsConstants.ETH_P_IP;
import static android.system.OsConstants.ETH_P_IPV6;
+import static com.android.networkstack.tethering.BpfUtils.DOWNSTREAM;
+import static com.android.networkstack.tethering.BpfUtils.UPSTREAM;
import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
import android.app.usage.NetworkStatsManager;
@@ -92,9 +94,6 @@ public class BpfCoordinator {
System.loadLibrary("tetherutilsjni");
}
- static final boolean DOWNSTREAM = true;
- static final boolean UPSTREAM = false;
-
private static final String TAG = BpfCoordinator.class.getSimpleName();
private static final int DUMP_TIMEOUT_MS = 10_000;
private static final MacAddress NULL_MAC_ADDRESS = MacAddress.fromString(
@@ -118,7 +117,6 @@ public class BpfCoordinator {
return makeMapPath((downstream ? "downstream" : "upstream") + ipVersion);
}
-
@VisibleForTesting
enum StatsType {
STATS_PER_IFACE,
@@ -218,12 +216,16 @@ public class BpfCoordinator {
// is okay for now because there have only one upstream generally.
private final HashMap<Inet4Address, Integer> mIpv4UpstreamIndices = new HashMap<>();
+ // Map for upstream and downstream pair.
+ private final HashMap<String, HashSet<String>> mForwardingPairs = new HashMap<>();
+
// Runnable that used by scheduling next polling of stats.
private final Runnable mScheduledPollingTask = () -> {
updateForwardedStats();
maybeSchedulePollingStats();
};
+ // TODO: add BpfMap<TetherDownstream64Key, TetherDownstream64Value> retrieving function.
@VisibleForTesting
public abstract static class Dependencies {
/** Get handler. */
@@ -259,6 +261,7 @@ public class BpfCoordinator {
/** Get downstream4 BPF map. */
@Nullable public BpfMap<Tether4Key, Tether4Value> getBpfDownstream4Map() {
+ if (!isAtLeastS()) return null;
try {
return new BpfMap<>(TETHER_DOWNSTREAM4_MAP_PATH,
BpfMap.BPF_F_RDWR, Tether4Key.class, Tether4Value.class);
@@ -270,6 +273,7 @@ public class BpfCoordinator {
/** Get upstream4 BPF map. */
@Nullable public BpfMap<Tether4Key, Tether4Value> getBpfUpstream4Map() {
+ if (!isAtLeastS()) return null;
try {
return new BpfMap<>(TETHER_UPSTREAM4_MAP_PATH,
BpfMap.BPF_F_RDWR, Tether4Key.class, Tether4Value.class);
@@ -281,6 +285,7 @@ public class BpfCoordinator {
/** Get downstream6 BPF map. */
@Nullable public BpfMap<TetherDownstream6Key, Tether6Value> getBpfDownstream6Map() {
+ if (!isAtLeastS()) return null;
try {
return new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH,
BpfMap.BPF_F_RDWR, TetherDownstream6Key.class, Tether6Value.class);
@@ -292,6 +297,7 @@ public class BpfCoordinator {
/** Get upstream6 BPF map. */
@Nullable public BpfMap<TetherUpstream6Key, Tether6Value> getBpfUpstream6Map() {
+ if (!isAtLeastS()) return null;
try {
return new BpfMap<>(TETHER_UPSTREAM6_FS_PATH, BpfMap.BPF_F_RDWR,
TetherUpstream6Key.class, Tether6Value.class);
@@ -303,6 +309,7 @@ public class BpfCoordinator {
/** Get stats BPF map. */
@Nullable public BpfMap<TetherStatsKey, TetherStatsValue> getBpfStatsMap() {
+ if (!isAtLeastS()) return null;
try {
return new BpfMap<>(TETHER_STATS_MAP_PATH,
BpfMap.BPF_F_RDWR, TetherStatsKey.class, TetherStatsValue.class);
@@ -314,6 +321,7 @@ public class BpfCoordinator {
/** Get limit BPF map. */
@Nullable public BpfMap<TetherLimitKey, TetherLimitValue> getBpfLimitMap() {
+ if (!isAtLeastS()) return null;
try {
return new BpfMap<>(TETHER_LIMIT_MAP_PATH,
BpfMap.BPF_F_RDWR, TetherLimitKey.class, TetherLimitValue.class);
@@ -418,7 +426,8 @@ public class BpfCoordinator {
* See NetlinkMonitor#handlePacket, NetlinkMessage#parseNfMessage.
*/
public void startMonitoring(@NonNull final IpServer ipServer) {
- if (!isUsingBpf()) return;
+ // TODO: Wrap conntrackMonitor starting function into mBpfCoordinatorShim.
+ if (!isUsingBpf() || !mDeps.isAtLeastS()) return;
if (mMonitoringIpServers.contains(ipServer)) {
Log.wtf(TAG, "The same downstream " + ipServer.interfaceName()
@@ -439,6 +448,9 @@ public class BpfCoordinator {
* Note that this can be only called on handler thread.
*/
public void stopMonitoring(@NonNull final IpServer ipServer) {
+ // TODO: Wrap conntrackMonitor stopping function into mBpfCoordinatorShim.
+ if (!isUsingBpf() || !mDeps.isAtLeastS()) return;
+
mMonitoringIpServers.remove(ipServer);
if (!mMonitoringIpServers.isEmpty()) return;
@@ -690,6 +702,37 @@ public class BpfCoordinator {
}
}
+ /**
+ * Attach BPF program
+ *
+ * TODO: consider error handling if the attach program failed.
+ */
+ public void maybeAttachProgram(@NonNull String intIface, @NonNull String extIface) {
+ if (forwardingPairExists(intIface, extIface)) return;
+
+ boolean firstDownstreamForThisUpstream = !isAnyForwardingPairOnUpstream(extIface);
+ forwardingPairAdd(intIface, extIface);
+
+ mBpfCoordinatorShim.attachProgram(intIface, UPSTREAM);
+ // Attach if the upstream is the first time to be used in a forwarding pair.
+ if (firstDownstreamForThisUpstream) {
+ mBpfCoordinatorShim.attachProgram(extIface, DOWNSTREAM);
+ }
+ }
+
+ /**
+ * Detach BPF program
+ */
+ public void maybeDetachProgram(@NonNull String intIface, @NonNull String extIface) {
+ forwardingPairRemove(intIface, extIface);
+
+ // Detaching program may fail because the interface has been removed already.
+ mBpfCoordinatorShim.detachProgram(intIface);
+ // Detach if no more forwarding pair is using the upstream.
+ if (!isAnyForwardingPairOnUpstream(extIface)) {
+ mBpfCoordinatorShim.detachProgram(extIface);
+ }
+ }
// TODO: make mInterfaceNames accessible to the shim and move this code to there.
private String getIfName(long ifindex) {
@@ -771,7 +814,7 @@ public class BpfCoordinator {
final int upstreamIfindex = rule.upstreamIfindex;
pw.println(String.format("%d(%s) %d(%s) %s %s %s", upstreamIfindex,
mInterfaceNames.get(upstreamIfindex), rule.downstreamIfindex,
- downstreamIface, rule.address, rule.srcMac, rule.dstMac));
+ downstreamIface, rule.address.getHostAddress(), rule.srcMac, rule.dstMac));
}
pw.decreaseIndent();
}
@@ -795,7 +838,7 @@ public class BpfCoordinator {
}
map.forEach((k, v) -> pw.println(ipv6UpstreamRuletoString(k, v)));
} catch (ErrnoException e) {
- pw.println("Error dumping IPv4 map: " + e);
+ pw.println("Error dumping IPv6 upstream map: " + e);
}
}
@@ -808,9 +851,10 @@ public class BpfCoordinator {
} catch (UnknownHostException impossible) {
throw new AssertionError("4-byte array not valid IPv4 address!");
}
- return String.format("%d(%s) %d(%s) %s:%d -> %s:%d -> %s:%d",
- key.iif, getIfName(key.iif), value.oif, getIfName(value.oif),
- private4, key.srcPort, public4, value.srcPort, dst4, key.dstPort);
+ return String.format("[%s] %d(%s) %s:%d -> %d(%s) %s:%d -> %s:%d",
+ key.dstMac, key.iif, getIfName(key.iif), private4, key.srcPort,
+ value.oif, getIfName(value.oif),
+ public4, value.srcPort, dst4, key.dstPort);
}
private void dumpIpv4ForwardingRules(IndentingPrintWriter pw) {
@@ -823,7 +867,7 @@ public class BpfCoordinator {
pw.println("No IPv4 rules");
return;
}
- pw.println("[IPv4]: iif(iface) oif(iface) src nat dst");
+ pw.println("IPv4: [inDstMac] iif(iface) src -> nat -> dst");
pw.increaseIndent();
map.forEach((k, v) -> pw.println(ipv4RuleToString(k, v)));
} catch (ErrnoException e) {
@@ -842,6 +886,10 @@ public class BpfCoordinator {
}
private void dumpCounters(@NonNull IndentingPrintWriter pw) {
+ if (!mDeps.isAtLeastS()) {
+ pw.println("No counter support");
+ return;
+ }
try (BpfMap<U32Struct, U32Struct> map = new BpfMap<>(TETHER_ERROR_MAP_PATH,
BpfMap.BPF_F_RDONLY, U32Struct.class, U32Struct.class)) {
@@ -1226,6 +1274,33 @@ public class BpfCoordinator {
return false;
}
+ private void forwardingPairAdd(@NonNull String intIface, @NonNull String extIface) {
+ if (!mForwardingPairs.containsKey(extIface)) {
+ mForwardingPairs.put(extIface, new HashSet<String>());
+ }
+ mForwardingPairs.get(extIface).add(intIface);
+ }
+
+ private void forwardingPairRemove(@NonNull String intIface, @NonNull String extIface) {
+ HashSet<String> downstreams = mForwardingPairs.get(extIface);
+ if (downstreams == null) return;
+ if (!downstreams.remove(intIface)) return;
+
+ if (downstreams.isEmpty()) {
+ mForwardingPairs.remove(extIface);
+ }
+ }
+
+ private boolean forwardingPairExists(@NonNull String intIface, @NonNull String extIface) {
+ if (!mForwardingPairs.containsKey(extIface)) return false;
+
+ return mForwardingPairs.get(extIface).contains(intIface);
+ }
+
+ private boolean isAnyForwardingPairOnUpstream(@NonNull String extIface) {
+ return mForwardingPairs.containsKey(extIface);
+ }
+
@NonNull
private NetworkStats buildNetworkStats(@NonNull StatsType type, int ifIndex,
@NonNull final ForwardedStats diff) {
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfUtils.java b/Tethering/src/com/android/networkstack/tethering/BpfUtils.java
new file mode 100644
index 0000000000..0b44249458
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/BpfUtils.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2021 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.networkstack.tethering;
+
+import static android.system.OsConstants.ETH_P_IP;
+import static android.system.OsConstants.ETH_P_IPV6;
+
+import android.net.util.InterfaceParams;
+
+import androidx.annotation.NonNull;
+
+import java.io.IOException;
+
+/**
+ * The classes and the methods for BPF utilization.
+ *
+ * {@hide}
+ */
+public class BpfUtils {
+ static {
+ System.loadLibrary("tetherutilsjni");
+ }
+
+ // For better code clarity when used for 'bool ingress' parameter.
+ static final boolean EGRESS = false;
+ static final boolean INGRESS = true;
+
+ // For better code clarify when used for 'bool downstream' parameter.
+ //
+ // This is talking about the direction of travel of the offloaded packets.
+ //
+ // Upstream means packets heading towards the internet/uplink (upload),
+ // thus for tethering this is attached to ingress on the downstream interface,
+ // while for clat this is attached to egress on the v4-* clat interface.
+ //
+ // Downstream means packets coming from the internet/uplink (download), thus
+ // for both clat and tethering this is attached to ingress on the upstream interface.
+ static final boolean DOWNSTREAM = true;
+ static final boolean UPSTREAM = false;
+
+ // The priority of clat/tether hooks - smaller is higher priority.
+ // TC tether is higher priority then TC clat to match XDP winning over TC.
+ // Sync from system/netd/server/OffloadUtils.h.
+ static final short PRIO_TETHER6 = 1;
+ static final short PRIO_TETHER4 = 2;
+ // note that the above must be lower than PRIO_CLAT from netd's OffloadUtils.cpp
+
+ private static String makeProgPath(boolean downstream, int ipVersion, boolean ether) {
+ String path = "/sys/fs/bpf/tethering/prog_offload_schedcls_tether_"
+ + (downstream ? "downstream" : "upstream")
+ + ipVersion + "_"
+ + (ether ? "ether" : "rawip");
+ return path;
+ }
+
+ /**
+ * Attach BPF program
+ *
+ * TODO: use interface index to replace interface name.
+ */
+ public static void attachProgram(@NonNull String iface, boolean downstream)
+ throws IOException {
+ final InterfaceParams params = InterfaceParams.getByName(iface);
+ if (params == null) {
+ throw new IOException("Fail to get interface params for interface " + iface);
+ }
+
+ boolean ether;
+ try {
+ ether = isEthernet(iface);
+ } catch (IOException e) {
+ throw new IOException("isEthernet(" + params.index + "[" + iface + "]) failure: " + e);
+ }
+
+ try {
+ // tc filter add dev .. ingress prio 1 protocol ipv6 bpf object-pinned /sys/fs/bpf/...
+ // direct-action
+ tcFilterAddDevBpf(params.index, INGRESS, PRIO_TETHER6, (short) ETH_P_IPV6,
+ makeProgPath(downstream, 6, ether));
+ } catch (IOException e) {
+ throw new IOException("tc filter add dev (" + params.index + "[" + iface
+ + "]) ingress prio PRIO_TETHER6 protocol ipv6 failure: " + e);
+ }
+
+ try {
+ // tc filter add dev .. ingress prio 2 protocol ip bpf object-pinned /sys/fs/bpf/...
+ // direct-action
+ tcFilterAddDevBpf(params.index, INGRESS, PRIO_TETHER4, (short) ETH_P_IP,
+ makeProgPath(downstream, 4, ether));
+ } catch (IOException e) {
+ throw new IOException("tc filter add dev (" + params.index + "[" + iface
+ + "]) ingress prio PRIO_TETHER4 protocol ip failure: " + e);
+ }
+ }
+
+ /**
+ * Detach BPF program
+ *
+ * TODO: use interface index to replace interface name.
+ */
+ public static void detachProgram(@NonNull String iface) throws IOException {
+ final InterfaceParams params = InterfaceParams.getByName(iface);
+ if (params == null) {
+ throw new IOException("Fail to get interface params for interface " + iface);
+ }
+
+ try {
+ // tc filter del dev .. ingress prio 1 protocol ipv6
+ tcFilterDelDev(params.index, INGRESS, PRIO_TETHER6, (short) ETH_P_IPV6);
+ } catch (IOException e) {
+ throw new IOException("tc filter del dev (" + params.index + "[" + iface
+ + "]) ingress prio PRIO_TETHER6 protocol ipv6 failure: " + e);
+ }
+
+ try {
+ // tc filter del dev .. ingress prio 2 protocol ip
+ tcFilterDelDev(params.index, INGRESS, PRIO_TETHER4, (short) ETH_P_IP);
+ } catch (IOException e) {
+ throw new IOException("tc filter del dev (" + params.index + "[" + iface
+ + "]) ingress prio PRIO_TETHER4 protocol ip failure: " + e);
+ }
+ }
+
+ private static native boolean isEthernet(String iface) throws IOException;
+
+ private static native void tcFilterAddDevBpf(int ifIndex, boolean ingress, short prio,
+ short proto, String bpfProgPath) throws IOException;
+
+ private static native void tcFilterDelDev(int ifIndex, boolean ingress, short prio,
+ short proto) throws IOException;
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index ac5857d1c8..c6e8fd631f 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -442,7 +442,8 @@ public class Tethering {
// NOTE: This is always invoked on the mLooper thread.
private void updateConfiguration() {
mConfig = mDeps.generateTetheringConfiguration(mContext, mLog, mActiveDataSubId);
- mUpstreamNetworkMonitor.updateMobileRequiresDun(mConfig.isDunRequired);
+ mUpstreamNetworkMonitor.setUpstreamConfig(mConfig.chooseUpstreamAutomatically,
+ mConfig.isDunRequired);
reportConfigurationChanged(mConfig.toStableParcelable());
}
@@ -1559,7 +1560,7 @@ public class Tethering {
config.preferredUpstreamIfaceTypes);
if (ns == null) {
if (tryCell) {
- mUpstreamNetworkMonitor.registerMobileNetworkRequest();
+ mUpstreamNetworkMonitor.setTryCell(true);
// We think mobile should be coming up; don't set a retry.
} else {
sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
@@ -1852,7 +1853,7 @@ public class Tethering {
// longer desired, release any mobile requests.
final boolean previousUpstreamWanted = updateUpstreamWanted();
if (previousUpstreamWanted && !mUpstreamWanted) {
- mUpstreamNetworkMonitor.releaseMobileNetworkRequest();
+ mUpstreamNetworkMonitor.setTryCell(false);
}
break;
}
diff --git a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
index b17065cb78..513f07c735 100644
--- a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
+++ b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
@@ -60,7 +60,7 @@ import java.util.Set;
* Calling #startObserveAllNetworks() to observe all networks. Listening all
* networks is necessary while the expression of preferred upstreams remains
* a list of legacy connectivity types. In future, this can be revisited.
- * Calling #registerMobileNetworkRequest() to bring up mobile DUN/HIPRI network.
+ * Calling #setTryCell() to request bringing up mobile DUN or HIPRI.
*
* The methods and data members of this class are only to be accessed and
* modified from the tethering main state machine thread. Any other
@@ -114,7 +114,14 @@ public class UpstreamNetworkMonitor {
private NetworkCallback mListenAllCallback;
private NetworkCallback mDefaultNetworkCallback;
private NetworkCallback mMobileNetworkCallback;
+
+ /** Whether Tethering has requested a cellular upstream. */
+ private boolean mTryCell;
+ /** Whether the carrier requires DUN. */
private boolean mDunRequired;
+ /** Whether automatic upstream selection is enabled. */
+ private boolean mAutoUpstream;
+
// Whether the current default upstream is mobile or not.
private boolean mIsDefaultCellularUpstream;
// The current system default network (not really used yet).
@@ -190,23 +197,49 @@ public class UpstreamNetworkMonitor {
mNetworkMap.clear();
}
- /** Setup or teardown DUN connection according to |dunRequired|. */
- public void updateMobileRequiresDun(boolean dunRequired) {
- final boolean valueChanged = (mDunRequired != dunRequired);
+ private void reevaluateUpstreamRequirements(boolean tryCell, boolean autoUpstream,
+ boolean dunRequired) {
+ final boolean mobileRequestRequired = tryCell && (dunRequired || !autoUpstream);
+ final boolean dunRequiredChanged = (mDunRequired != dunRequired);
+
+ mTryCell = tryCell;
mDunRequired = dunRequired;
- if (valueChanged && mobileNetworkRequested()) {
- releaseMobileNetworkRequest();
+ mAutoUpstream = autoUpstream;
+
+ if (mobileRequestRequired && !mobileNetworkRequested()) {
registerMobileNetworkRequest();
+ } else if (mobileNetworkRequested() && !mobileRequestRequired) {
+ releaseMobileNetworkRequest();
+ } else if (mobileNetworkRequested() && dunRequiredChanged) {
+ releaseMobileNetworkRequest();
+ if (mobileRequestRequired) {
+ registerMobileNetworkRequest();
+ }
}
}
+ /**
+ * Informs UpstreamNetworkMonitor that a cellular upstream is desired.
+ *
+ * This may result in filing a NetworkRequest for DUN if it is required, or for MOBILE_HIPRI if
+ * automatic upstream selection is disabled and MOBILE_HIPRI is the preferred upstream.
+ */
+ public void setTryCell(boolean tryCell) {
+ reevaluateUpstreamRequirements(tryCell, mAutoUpstream, mDunRequired);
+ }
+
+ /** Informs UpstreamNetworkMonitor of upstream configuration parameters. */
+ public void setUpstreamConfig(boolean autoUpstream, boolean dunRequired) {
+ reevaluateUpstreamRequirements(mTryCell, autoUpstream, dunRequired);
+ }
+
/** Whether mobile network is requested. */
public boolean mobileNetworkRequested() {
return (mMobileNetworkCallback != null);
}
/** Request mobile network if mobile upstream is permitted. */
- public void registerMobileNetworkRequest() {
+ private void registerMobileNetworkRequest() {
if (!isCellularUpstreamPermitted()) {
mLog.i("registerMobileNetworkRequest() is not permitted");
releaseMobileNetworkRequest();
@@ -241,14 +274,16 @@ public class UpstreamNetworkMonitor {
// TODO: Change the timeout from 0 (no onUnavailable callback) to some
// moderate callback timeout. This might be useful for updating some UI.
// Additionally, we log a message to aid in any subsequent debugging.
- mLog.i("requesting mobile upstream network: " + mobileUpstreamRequest);
+ mLog.i("requesting mobile upstream network: " + mobileUpstreamRequest
+ + " mTryCell=" + mTryCell + " mAutoUpstream=" + mAutoUpstream
+ + " mDunRequired=" + mDunRequired);
cm().requestNetwork(mobileUpstreamRequest, 0, legacyType, mHandler,
mMobileNetworkCallback);
}
/** Release mobile network request. */
- public void releaseMobileNetworkRequest() {
+ private void releaseMobileNetworkRequest() {
if (mMobileNetworkCallback == null) return;
cm().unregisterNetworkCallback(mMobileNetworkCallback);
diff --git a/Tethering/tests/mts/Android.bp b/Tethering/tests/mts/Android.bp
index 20a5f1855e..edb6356a4a 100644
--- a/Tethering/tests/mts/Android.bp
+++ b/Tethering/tests/mts/Android.bp
@@ -52,7 +52,7 @@ android_test {
// Tag this module as a mts test artifact
test_suites: [
"general-tests",
- "mts",
+ "mts-tethering",
],
// Include both the 32 and 64 bit versions
diff --git a/Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java b/Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java
new file mode 100644
index 0000000000..14dae5c562
--- /dev/null
+++ b/Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ip;
+
+import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_MTU;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_RDNSS;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_RA_HEADER_LEN;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.net.INetd;
+import android.net.IpPrefix;
+import android.net.MacAddress;
+import android.net.ip.RouterAdvertisementDaemon.RaParams;
+import android.net.util.InterfaceParams;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.Ipv6Utils;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.LlaOption;
+import com.android.net.module.util.structs.MtuOption;
+import com.android.net.module.util.structs.PrefixInformationOption;
+import com.android.net.module.util.structs.RaHeader;
+import com.android.net.module.util.structs.RdnssOption;
+import com.android.testutils.TapPacketReader;
+import com.android.testutils.TapPacketReaderRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.HashSet;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class RouterAdvertisementDaemonTest {
+ private static final String TAG = RouterAdvertisementDaemonTest.class.getSimpleName();
+ private static final int DATA_BUFFER_LEN = 4096;
+ private static final int PACKET_TIMEOUT_MS = 5_000;
+
+ @Rule
+ public final TapPacketReaderRule mTetheredReader = new TapPacketReaderRule(
+ DATA_BUFFER_LEN, false /* autoStart */);
+
+ private InterfaceParams mTetheredParams;
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+ private TapPacketReader mTetheredPacketReader;
+ private RouterAdvertisementDaemon mRaDaemon;
+
+ private static INetd sNetd;
+
+ @BeforeClass
+ public static void setupOnce() {
+ System.loadLibrary("tetherutilsjni");
+
+ final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+ final IBinder netdIBinder =
+ (IBinder) inst.getContext().getSystemService(Context.NETD_SERVICE);
+ sNetd = INetd.Stub.asInterface(netdIBinder);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mHandlerThread = new HandlerThread(getClass().getSimpleName());
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+
+ setupTapInterfaces();
+
+ // Looper must be prepared here since AndroidJUnitRunner runs tests on separate threads.
+ if (Looper.myLooper() == null) Looper.prepare();
+
+ mRaDaemon = new RouterAdvertisementDaemon(mTetheredParams);
+ sNetd.networkAddInterface(INetd.LOCAL_NET_ID, mTetheredParams.name);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mTetheredReader.stop();
+ if (mHandlerThread != null) {
+ mHandlerThread.quitSafely();
+ mHandlerThread.join(PACKET_TIMEOUT_MS);
+ }
+
+ if (mTetheredParams != null) {
+ sNetd.networkRemoveInterface(INetd.LOCAL_NET_ID, mTetheredParams.name);
+ }
+ }
+
+ private void setupTapInterfaces() {
+ // Create tethered test iface.
+ mTetheredReader.start(mHandler);
+ mTetheredParams = InterfaceParams.getByName(mTetheredReader.iface.getInterfaceName());
+ assertNotNull(mTetheredParams);
+ mTetheredPacketReader = mTetheredReader.getReader();
+ mHandler.post(mTetheredPacketReader::start);
+ }
+
+ private class TestRaPacket {
+ final RaParams mNewParams, mOldParams;
+
+ TestRaPacket(final RaParams oldParams, final RaParams newParams) {
+ mOldParams = oldParams;
+ mNewParams = newParams;
+ }
+
+ public boolean isPacketMatched(final byte[] pkt) throws Exception {
+ if (pkt.length < (ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_RA_HEADER_LEN)) {
+ return false;
+ }
+ final ByteBuffer buf = ByteBuffer.wrap(pkt);
+
+ // Parse Ethernet header
+ final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
+ if (ethHdr.etherType != ETHER_TYPE_IPV6) return false;
+
+ // Parse IPv6 header
+ final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
+ assertEquals((ipv6Hdr.vtf >> 28), 6 /* ip version*/);
+
+ final int payLoadLength = pkt.length - ETHER_HEADER_LEN - IPV6_HEADER_LEN;
+ assertEquals(payLoadLength, ipv6Hdr.payloadLength);
+
+ // Parse ICMPv6 header
+ final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, buf);
+ if (icmpv6Hdr.type != (short) ICMPV6_ROUTER_ADVERTISEMENT) return false;
+
+ // Parse RA header
+ final RaHeader raHdr = Struct.parse(RaHeader.class, buf);
+ assertEquals(mNewParams.hopLimit, raHdr.hopLimit);
+
+ while (buf.position() < pkt.length) {
+ final int currentPos = buf.position();
+ final int type = Byte.toUnsignedInt(buf.get());
+ final int length = Byte.toUnsignedInt(buf.get());
+ switch (type) {
+ case ICMPV6_ND_OPTION_PIO:
+ assertEquals(4, length);
+
+ final ByteBuffer pioBuf = ByteBuffer.wrap(buf.array(), currentPos,
+ Struct.getSize(PrefixInformationOption.class));
+ final PrefixInformationOption pio =
+ Struct.parse(PrefixInformationOption.class, pioBuf);
+ assertEquals((byte) 0xc0, pio.flags); // L & A set
+
+ final InetAddress address = InetAddress.getByAddress(pio.prefix);
+ final IpPrefix prefix = new IpPrefix(address, pio.prefixLen);
+ if (mNewParams.prefixes.contains(prefix)) {
+ assertTrue(pio.validLifetime > 0);
+ assertTrue(pio.preferredLifetime > 0);
+ } else if (mOldParams != null && mOldParams.prefixes.contains(prefix)) {
+ assertEquals(0, pio.validLifetime);
+ assertEquals(0, pio.preferredLifetime);
+ } else {
+ fail("Unepxected prefix: " + prefix);
+ }
+
+ // Move ByteBuffer position to the next option.
+ buf.position(currentPos + Struct.getSize(PrefixInformationOption.class));
+ break;
+ case ICMPV6_ND_OPTION_MTU:
+ assertEquals(1, length);
+
+ final ByteBuffer mtuBuf = ByteBuffer.wrap(buf.array(), currentPos,
+ Struct.getSize(MtuOption.class));
+ final MtuOption mtu = Struct.parse(MtuOption.class, mtuBuf);
+ assertEquals(mNewParams.mtu, mtu.mtu);
+
+ // Move ByteBuffer position to the next option.
+ buf.position(currentPos + Struct.getSize(MtuOption.class));
+ break;
+ case ICMPV6_ND_OPTION_RDNSS:
+ final int rdnssHeaderLen = Struct.getSize(RdnssOption.class);
+ final ByteBuffer RdnssBuf = ByteBuffer.wrap(buf.array(), currentPos,
+ rdnssHeaderLen);
+ final RdnssOption rdnss = Struct.parse(RdnssOption.class, RdnssBuf);
+ final String msg =
+ rdnss.lifetime > 0 ? "Unknown dns" : "Unknown deprecated dns";
+ final HashSet<Inet6Address> dnses =
+ rdnss.lifetime > 0 ? mNewParams.dnses : mOldParams.dnses;
+ assertNotNull(msg, dnses);
+
+ // Check DNS servers included in this option.
+ buf.position(currentPos + rdnssHeaderLen); // skip the rdnss option header
+ final int numOfDnses = (length - 1) / 2;
+ for (int i = 0; i < numOfDnses; i++) {
+ byte[] rawAddress = new byte[IPV6_ADDR_LEN];
+ buf.get(rawAddress);
+ final Inet6Address dns =
+ (Inet6Address) InetAddress.getByAddress(rawAddress);
+ if (!dnses.contains(dns)) fail("Unexpected dns: " + dns);
+ }
+ // Unnecessary to move ByteBuffer position here, since the position has been
+ // moved forward correctly after reading DNS servers from ByteBuffer.
+ break;
+ case ICMPV6_ND_OPTION_SLLA:
+ // Do nothing, just move ByteBuffer position to the next option.
+ buf.position(currentPos + Struct.getSize(LlaOption.class));
+ break;
+ default:
+ fail("Unknown RA option type " + type);
+ }
+ }
+ return true;
+ }
+ }
+
+ private RaParams createRaParams(final String ipv6Address) throws Exception {
+ final RaParams params = new RaParams();
+ final Inet6Address address = (Inet6Address) InetAddress.getByName(ipv6Address);
+ params.dnses.add(address);
+ params.prefixes.add(new IpPrefix(address, 64));
+
+ return params;
+ }
+
+ private boolean assertRaPacket(final TestRaPacket testRa)
+ throws Exception {
+ byte[] packet;
+ while ((packet = mTetheredPacketReader.poll(PACKET_TIMEOUT_MS)) != null) {
+ if (testRa.isPacketMatched(packet)) return true;
+ }
+ return false;
+ }
+
+ private ByteBuffer createRsPacket(final String srcIp) throws Exception {
+ final MacAddress dstMac = MacAddress.fromString("33:33:03:04:05:06");
+ final MacAddress srcMac = mTetheredParams.macAddr;
+ final ByteBuffer slla = LlaOption.build((byte) ICMPV6_ND_OPTION_SLLA, srcMac);
+
+ return Ipv6Utils.buildRsPacket(srcMac, dstMac, (Inet6Address) InetAddress.getByName(srcIp),
+ IPV6_ADDR_ALL_NODES_MULTICAST, slla);
+ }
+
+ @Test
+ public void testUnSolicitRouterAdvertisement() throws Exception {
+ assertTrue(mRaDaemon.start());
+ final RaParams params1 = createRaParams("2001:1122:3344::5566");
+ mRaDaemon.buildNewRa(null, params1);
+ assertRaPacket(new TestRaPacket(null, params1));
+
+ final RaParams params2 = createRaParams("2006:3344:5566::7788");
+ mRaDaemon.buildNewRa(params1, params2);
+ assertRaPacket(new TestRaPacket(params1, params2));
+ }
+
+ @Test
+ public void testSolicitRouterAdvertisement() throws Exception {
+ assertTrue(mRaDaemon.start());
+ final RaParams params1 = createRaParams("2001:1122:3344::5566");
+ mRaDaemon.buildNewRa(null, params1);
+ assertRaPacket(new TestRaPacket(null, params1));
+
+ final ByteBuffer rs = createRsPacket("fe80::1122:3344:5566:7788");
+ mTetheredPacketReader.sendResponse(rs);
+ assertRaPacket(new TestRaPacket(null, params1));
+ }
+}
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index b45db7edff..adf1f671ca 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -247,7 +247,7 @@ public class IpServerTest {
lp.setInterfaceName(upstreamIface);
dispatchTetherConnectionChanged(upstreamIface, lp, 0);
}
- reset(mNetd, mCallback, mAddressCoordinator);
+ reset(mNetd, mCallback, mAddressCoordinator, mBpfCoordinator);
when(mAddressCoordinator.requestDownstreamAddress(any(), anyBoolean())).thenReturn(
mTestAddress);
}
@@ -471,10 +471,14 @@ public class IpServerTest {
// Telling the state machine about its upstream interface triggers
// a little more configuration.
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
- InOrder inOrder = inOrder(mNetd);
+ InOrder inOrder = inOrder(mNetd, mBpfCoordinator);
+
+ // Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>.
+ inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
- verifyNoMoreInteractions(mNetd, mCallback);
+
+ verifyNoMoreInteractions(mNetd, mCallback, mBpfCoordinator);
}
@Test
@@ -482,12 +486,19 @@ public class IpServerTest {
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
- InOrder inOrder = inOrder(mNetd);
+ InOrder inOrder = inOrder(mNetd, mBpfCoordinator);
+
+ // Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>.
+ inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+
+ // Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2>.
+ inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
- verifyNoMoreInteractions(mNetd, mCallback);
+
+ verifyNoMoreInteractions(mNetd, mCallback, mBpfCoordinator);
}
@Test
@@ -497,10 +508,20 @@ public class IpServerTest {
doThrow(RemoteException.class).when(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
- InOrder inOrder = inOrder(mNetd);
+ InOrder inOrder = inOrder(mNetd, mBpfCoordinator);
+
+ // Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>.
+ inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+
+ // Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> and expect that failed on
+ // tetherAddForward.
+ inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
+
+ // Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> to fallback.
+ inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
}
@@ -513,11 +534,21 @@ public class IpServerTest {
IFACE_NAME, UPSTREAM_IFACE2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
- InOrder inOrder = inOrder(mNetd);
+ InOrder inOrder = inOrder(mNetd, mBpfCoordinator);
+
+ // Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>.
+ inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+
+ // Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> and expect that failed on
+ // ipfwdAddInterfaceForward.
+ inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
+
+ // Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> to fallback.
+ inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
}
@@ -527,19 +558,22 @@ public class IpServerTest {
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
- InOrder inOrder = inOrder(mNetd, mCallback, mAddressCoordinator);
+ InOrder inOrder = inOrder(mNetd, mCallback, mAddressCoordinator, mBpfCoordinator);
+ inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer);
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
inOrder.verify(mAddressCoordinator).releaseDownstream(any());
+ inOrder.verify(mBpfCoordinator).stopMonitoring(mIpServer);
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), any(LinkProperties.class));
- verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator);
+ verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator, mBpfCoordinator);
}
@Test
diff --git a/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java b/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java
index 91c7771cc7..9968b5f173 100644
--- a/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java
+++ b/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java
@@ -17,27 +17,49 @@ package android.net.util;
import static android.net.TetheringManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.EAGAIN;
+import static android.system.OsConstants.SOCK_CLOEXEC;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_NONBLOCK;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import android.net.LinkAddress;
+import android.net.MacAddress;
import android.net.TetheringRequestParcel;
+import android.system.ErrnoException;
+import android.system.Os;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.net.module.util.Ipv6Utils;
+import com.android.net.module.util.NetworkStackConstants;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv6Header;
import com.android.testutils.MiscAsserts;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.FileDescriptor;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class TetheringUtilsTest {
private static final LinkAddress TEST_SERVER_ADDR = new LinkAddress("192.168.43.1/24");
private static final LinkAddress TEST_CLIENT_ADDR = new LinkAddress("192.168.43.5/24");
+ private static final int PACKET_SIZE = 1500;
+
private TetheringRequestParcel mTetheringRequest;
@Before
@@ -84,4 +106,82 @@ public class TetheringUtilsTest {
MiscAsserts.assertFieldCountEquals(5, TetheringRequestParcel.class);
}
+
+ // Writes the specified packet to a filedescriptor, skipping the Ethernet header.
+ // Needed because the Ipv6Utils methods for building packets always include the Ethernet header,
+ // but socket filters applied by TetheringUtils expect the packet to start from the IP header.
+ private int writePacket(FileDescriptor fd, ByteBuffer pkt) throws Exception {
+ pkt.flip();
+ int offset = Struct.getSize(EthernetHeader.class);
+ int len = pkt.capacity() - offset;
+ return Os.write(fd, pkt.array(), offset, len);
+ }
+
+ // Reads a packet from the filedescriptor.
+ private ByteBuffer readIpPacket(FileDescriptor fd) throws Exception {
+ ByteBuffer buf = ByteBuffer.allocate(PACKET_SIZE);
+ Os.read(fd, buf);
+ return buf;
+ }
+
+ private interface SocketFilter {
+ void apply(FileDescriptor fd) throws Exception;
+ }
+
+ private ByteBuffer checkIcmpSocketFilter(ByteBuffer passed, ByteBuffer dropped,
+ SocketFilter filter) throws Exception {
+ FileDescriptor in = new FileDescriptor();
+ FileDescriptor out = new FileDescriptor();
+ Os.socketpair(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, in, out);
+
+ // Before the filter is applied, it doesn't drop anything.
+ int len = writePacket(out, dropped);
+ ByteBuffer received = readIpPacket(in);
+ assertEquals(len, received.position());
+
+ // Install the socket filter. Then write two packets, the first expected to be dropped and
+ // the second expected to be passed. Check that only the second makes it through.
+ filter.apply(in);
+ writePacket(out, dropped);
+ len = writePacket(out, passed);
+ received = readIpPacket(in);
+ assertEquals(len, received.position());
+ received.flip();
+
+ // Check there are no more packets to read.
+ try {
+ readIpPacket(in);
+ } catch (ErrnoException expected) {
+ assertEquals(EAGAIN, expected.errno);
+ }
+
+ return received;
+ }
+
+ @Test
+ public void testIcmpSocketFilters() throws Exception {
+ MacAddress mac1 = MacAddress.fromString("11:22:33:44:55:66");
+ MacAddress mac2 = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
+ Inet6Address ll1 = (Inet6Address) InetAddress.getByName("fe80::1");
+ Inet6Address ll2 = (Inet6Address) InetAddress.getByName("fe80::abcd");
+ Inet6Address allRouters = NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
+
+ final ByteBuffer na = Ipv6Utils.buildNaPacket(mac1, mac2, ll1, ll2, 0, ll1);
+ final ByteBuffer ns = Ipv6Utils.buildNsPacket(mac1, mac2, ll1, ll2, ll1);
+ final ByteBuffer rs = Ipv6Utils.buildRsPacket(mac1, mac2, ll1, allRouters);
+
+ ByteBuffer received = checkIcmpSocketFilter(na /* passed */, rs /* dropped */,
+ TetheringUtils::setupNaSocket);
+
+ Struct.parse(Ipv6Header.class, received); // Skip IPv6 header.
+ Icmpv6Header icmpv6 = Struct.parse(Icmpv6Header.class, received);
+ assertEquals(NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT, icmpv6.type);
+
+ received = checkIcmpSocketFilter(ns /* passed */, rs /* dropped */,
+ TetheringUtils::setupNsSocket);
+
+ Struct.parse(Ipv6Header.class, received); // Skip IPv6 header.
+ icmpv6 = Struct.parse(Icmpv6Header.class, received);
+ assertEquals(NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION, icmpv6.type);
+ }
}
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 1270e50c42..293d0df67e 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -26,9 +26,12 @@ import static android.net.NetworkStats.UID_TETHERING;
import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
import static android.system.OsConstants.ETH_P_IPV6;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
import static com.android.networkstack.tethering.BpfCoordinator.StatsType;
import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_IFACE;
import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_UID;
+import static com.android.networkstack.tethering.BpfUtils.DOWNSTREAM;
+import static com.android.networkstack.tethering.BpfUtils.UPSTREAM;
import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
import static org.junit.Assert.assertEquals;
@@ -70,13 +73,17 @@ import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.net.module.util.NetworkStackConstants;
import com.android.net.module.util.Struct;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.TestableNetworkStatsProviderCbBinder;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -84,6 +91,7 @@ import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -97,6 +105,9 @@ import java.util.function.BiConsumer;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class BpfCoordinatorTest {
+ @Rule
+ public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
private static final int DOWNSTREAM_IFINDEX = 10;
private static final MacAddress DOWNSTREAM_MAC = MacAddress.ALL_ZEROS_ADDRESS;
private static final InetAddress NEIGH_A = InetAddresses.parseNumericAddress("2001:db8::1");
@@ -150,6 +161,12 @@ public class BpfCoordinatorTest {
// BpfMap#getValue treats that the entry is not found as no error.
return mMap.get(key);
}
+
+ @Override
+ public void clear() throws ErrnoException {
+ // TODO: consider using mocked #getFirstKey and #deleteEntry to implement.
+ mMap.clear();
+ }
};
@Mock private NetworkStatsManager mStatsManager;
@@ -988,6 +1005,24 @@ public class BpfCoordinatorTest {
@Test
@IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testBpfDisabledbyNoBpfDownstream4Map() throws Exception {
+ setupFunctioningNetdInterface();
+ doReturn(null).when(mDeps).getBpfDownstream4Map();
+
+ checkBpfDisabled();
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testBpfDisabledbyNoBpfUpstream4Map() throws Exception {
+ setupFunctioningNetdInterface();
+ doReturn(null).when(mDeps).getBpfUpstream4Map();
+
+ checkBpfDisabled();
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
public void testBpfDisabledbyNoBpfStatsMap() throws Exception {
setupFunctioningNetdInterface();
doReturn(null).when(mDeps).getBpfStatsMap();
@@ -1005,6 +1040,73 @@ public class BpfCoordinatorTest {
}
@Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testBpfMapClear() throws Exception {
+ setupFunctioningNetdInterface();
+
+ final BpfCoordinator coordinator = makeBpfCoordinator();
+ verify(mBpfDownstream4Map).clear();
+ verify(mBpfUpstream4Map).clear();
+ verify(mBpfDownstream6Map).clear();
+ verify(mBpfUpstream6Map).clear();
+ verify(mBpfStatsMap).clear();
+ verify(mBpfLimitMap).clear();
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testAttachDetachBpfProgram() throws Exception {
+ setupFunctioningNetdInterface();
+
+ // Static mocking for BpfUtils.
+ MockitoSession mockSession = ExtendedMockito.mockitoSession()
+ .mockStatic(BpfUtils.class)
+ .startMocking();
+ try {
+ final String intIface1 = "wlan1";
+ final String intIface2 = "rndis0";
+ final String extIface = "rmnet_data0";
+ final BpfUtils mockMarkerBpfUtils = staticMockMarker(BpfUtils.class);
+ final BpfCoordinator coordinator = makeBpfCoordinator();
+
+ // [1] Add the forwarding pair <wlan1, rmnet_data0>. Expect that attach both wlan1 and
+ // rmnet_data0.
+ coordinator.maybeAttachProgram(intIface1, extIface);
+ ExtendedMockito.verify(() -> BpfUtils.attachProgram(extIface, DOWNSTREAM));
+ ExtendedMockito.verify(() -> BpfUtils.attachProgram(intIface1, UPSTREAM));
+ ExtendedMockito.verifyNoMoreInteractions(mockMarkerBpfUtils);
+ ExtendedMockito.clearInvocations(mockMarkerBpfUtils);
+
+ // [2] Add the forwarding pair <wlan1, rmnet_data0> again. Expect no more action.
+ coordinator.maybeAttachProgram(intIface1, extIface);
+ ExtendedMockito.verifyNoMoreInteractions(mockMarkerBpfUtils);
+ ExtendedMockito.clearInvocations(mockMarkerBpfUtils);
+
+ // [3] Add the forwarding pair <rndis0, rmnet_data0>. Expect that attach rndis0 only.
+ coordinator.maybeAttachProgram(intIface2, extIface);
+ ExtendedMockito.verify(() -> BpfUtils.attachProgram(intIface2, UPSTREAM));
+ ExtendedMockito.verifyNoMoreInteractions(mockMarkerBpfUtils);
+ ExtendedMockito.clearInvocations(mockMarkerBpfUtils);
+
+ // [4] Remove the forwarding pair <rndis0, rmnet_data0>. Expect detach rndis0 only.
+ coordinator.maybeDetachProgram(intIface2, extIface);
+ ExtendedMockito.verify(() -> BpfUtils.detachProgram(intIface2));
+ ExtendedMockito.verifyNoMoreInteractions(mockMarkerBpfUtils);
+ ExtendedMockito.clearInvocations(mockMarkerBpfUtils);
+
+ // [5] Remove the forwarding pair <wlan1, rmnet_data0>. Expect that detach both wlan1
+ // and rmnet_data0.
+ coordinator.maybeDetachProgram(intIface1, extIface);
+ ExtendedMockito.verify(() -> BpfUtils.detachProgram(extIface));
+ ExtendedMockito.verify(() -> BpfUtils.detachProgram(intIface1));
+ ExtendedMockito.verifyNoMoreInteractions(mockMarkerBpfUtils);
+ ExtendedMockito.clearInvocations(mockMarkerBpfUtils);
+ } finally {
+ mockSession.finishMocking();
+ }
+ }
+
+ @Test
public void testTetheringConfigSetPollingInterval() throws Exception {
setupFunctioningNetdInterface();
@@ -1054,6 +1156,7 @@ public class BpfCoordinatorTest {
}
@Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
public void testStartStopConntrackMonitoring() throws Exception {
setupFunctioningNetdInterface();
@@ -1074,6 +1177,23 @@ public class BpfCoordinatorTest {
}
@Test
+ @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @IgnoreAfter(Build.VERSION_CODES.R)
+ // Only run this test on Android R.
+ public void testStartStopConntrackMonitoring_R() throws Exception {
+ setupFunctioningNetdInterface();
+
+ final BpfCoordinator coordinator = makeBpfCoordinator();
+
+ coordinator.startMonitoring(mIpServer);
+ verify(mConntrackMonitor, never()).start();
+
+ coordinator.stopMonitoring(mIpServer);
+ verify(mConntrackMonitor, never()).stop();
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
public void testStartStopConntrackMonitoringWithTwoDownstreamIfaces() throws Exception {
setupFunctioningNetdInterface();
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java
index 3a6350c026..a7287a2d5c 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java
@@ -16,29 +16,32 @@
package com.android.networkstack.tethering;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import android.content.Context;
+import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.os.Handler;
+import android.os.UserHandle;
+import android.util.ArrayMap;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
-import java.util.Set;
/**
* Simulates upstream switching and sending NetworkCallbacks and CONNECTIVITY_ACTION broadcasts.
*
+ * Unlike any real networking code, this class is single-threaded and entirely synchronous.
+ * The effects of all method calls (including sending fake broadcasts, sending callbacks, etc.) are
+ * performed immediately on the caller's thread before returning.
+ *
* TODO: this duplicates a fair amount of code from ConnectivityManager and ConnectivityService.
* Consider using a ConnectivityService object instead, as used in ConnectivityServiceTest.
*
@@ -55,44 +58,63 @@ import java.util.Set;
* that state changes), this may become less important or unnecessary.
*/
public class TestConnectivityManager extends ConnectivityManager {
- public Map<NetworkCallback, Handler> allCallbacks = new HashMap<>();
- public Set<NetworkCallback> trackingDefault = new HashSet<>();
- public TestNetworkAgent defaultNetwork = null;
- public Map<NetworkCallback, NetworkRequest> listening = new HashMap<>();
- public Map<NetworkCallback, NetworkRequest> requested = new HashMap<>();
- public Map<NetworkCallback, Integer> legacyTypeMap = new HashMap<>();
+ final Map<NetworkCallback, NetworkRequestInfo> mAllCallbacks = new ArrayMap<>();
+ final Map<NetworkCallback, NetworkRequestInfo> mTrackingDefault = new ArrayMap<>();
+ final Map<NetworkCallback, NetworkRequestInfo> mListening = new ArrayMap<>();
+ final Map<NetworkCallback, NetworkRequestInfo> mRequested = new ArrayMap<>();
+ final Map<NetworkCallback, Integer> mLegacyTypeMap = new ArrayMap<>();
private final NetworkRequest mDefaultRequest;
- private int mNetworkId = 100;
+ private final Context mContext;
+ private int mNetworkId = 100;
+ private TestNetworkAgent mDefaultNetwork = null;
+
+ /**
+ * Constructs a TestConnectivityManager.
+ * @param ctx the context to use. Must be a fake or a mock because otherwise the test will
+ * attempt to send real broadcasts and resulting in permission denials.
+ * @param svc an IConnectivityManager. Should be a fake or a mock.
+ * @param defaultRequest the default NetworkRequest that will be used by Tethering.
+ */
public TestConnectivityManager(Context ctx, IConnectivityManager svc,
NetworkRequest defaultRequest) {
super(ctx, svc);
+ mContext = ctx;
mDefaultRequest = defaultRequest;
}
+ class NetworkRequestInfo {
+ public final NetworkRequest request;
+ public final Handler handler;
+ NetworkRequestInfo(NetworkRequest r, Handler h) {
+ request = r;
+ handler = h;
+ }
+ }
+
boolean hasNoCallbacks() {
- return allCallbacks.isEmpty()
- && trackingDefault.isEmpty()
- && listening.isEmpty()
- && requested.isEmpty()
- && legacyTypeMap.isEmpty();
+ return mAllCallbacks.isEmpty()
+ && mTrackingDefault.isEmpty()
+ && mListening.isEmpty()
+ && mRequested.isEmpty()
+ && mLegacyTypeMap.isEmpty();
}
boolean onlyHasDefaultCallbacks() {
- return (allCallbacks.size() == 1)
- && (trackingDefault.size() == 1)
- && listening.isEmpty()
- && requested.isEmpty()
- && legacyTypeMap.isEmpty();
+ return (mAllCallbacks.size() == 1)
+ && (mTrackingDefault.size() == 1)
+ && mListening.isEmpty()
+ && mRequested.isEmpty()
+ && mLegacyTypeMap.isEmpty();
}
boolean isListeningForAll() {
final NetworkCapabilities empty = new NetworkCapabilities();
empty.clearAll();
- for (NetworkRequest req : listening.values()) {
- if (req.networkCapabilities.equalRequestableCapabilities(empty)) {
+ for (NetworkRequestInfo nri : mListening.values()) {
+ if (nri.request.networkCapabilities.equalRequestableCapabilities(empty)) {
return true;
}
}
@@ -103,33 +125,52 @@ public class TestConnectivityManager extends ConnectivityManager {
return ++mNetworkId;
}
- void makeDefaultNetwork(TestNetworkAgent agent) {
- if (Objects.equals(defaultNetwork, agent)) return;
-
- final TestNetworkAgent formerDefault = defaultNetwork;
- defaultNetwork = agent;
+ private void sendDefaultNetworkBroadcasts(TestNetworkAgent formerDefault,
+ TestNetworkAgent defaultNetwork) {
+ if (formerDefault != null) {
+ sendConnectivityAction(formerDefault.legacyType, false /* connected */);
+ }
+ if (defaultNetwork != null) {
+ sendConnectivityAction(defaultNetwork.legacyType, true /* connected */);
+ }
+ }
- for (NetworkCallback cb : trackingDefault) {
+ private void sendDefaultNetworkCallbacks(TestNetworkAgent formerDefault,
+ TestNetworkAgent defaultNetwork) {
+ for (NetworkCallback cb : mTrackingDefault.keySet()) {
+ final NetworkRequestInfo nri = mTrackingDefault.get(cb);
if (defaultNetwork != null) {
- cb.onAvailable(defaultNetwork.networkId);
- cb.onCapabilitiesChanged(
- defaultNetwork.networkId, defaultNetwork.networkCapabilities);
- cb.onLinkPropertiesChanged(
- defaultNetwork.networkId, defaultNetwork.linkProperties);
+ nri.handler.post(() -> cb.onAvailable(defaultNetwork.networkId));
+ nri.handler.post(() -> cb.onCapabilitiesChanged(
+ defaultNetwork.networkId, defaultNetwork.networkCapabilities));
+ nri.handler.post(() -> cb.onLinkPropertiesChanged(
+ defaultNetwork.networkId, defaultNetwork.linkProperties));
+ } else if (formerDefault != null) {
+ nri.handler.post(() -> cb.onLost(formerDefault.networkId));
}
}
}
+ void makeDefaultNetwork(TestNetworkAgent agent) {
+ if (Objects.equals(mDefaultNetwork, agent)) return;
+
+ final TestNetworkAgent formerDefault = mDefaultNetwork;
+ mDefaultNetwork = agent;
+
+ sendDefaultNetworkCallbacks(formerDefault, mDefaultNetwork);
+ sendDefaultNetworkBroadcasts(formerDefault, mDefaultNetwork);
+ }
+
@Override
public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) {
- assertFalse(allCallbacks.containsKey(cb));
- allCallbacks.put(cb, h);
+ assertFalse(mAllCallbacks.containsKey(cb));
+ mAllCallbacks.put(cb, new NetworkRequestInfo(req, h));
if (mDefaultRequest.equals(req)) {
- assertFalse(trackingDefault.contains(cb));
- trackingDefault.add(cb);
+ assertFalse(mTrackingDefault.containsKey(cb));
+ mTrackingDefault.put(cb, new NetworkRequestInfo(req, h));
} else {
- assertFalse(requested.containsKey(cb));
- requested.put(cb, req);
+ assertFalse(mRequested.containsKey(cb));
+ mRequested.put(cb, new NetworkRequestInfo(req, h));
}
}
@@ -141,22 +182,22 @@ public class TestConnectivityManager extends ConnectivityManager {
@Override
public void requestNetwork(NetworkRequest req,
int timeoutMs, int legacyType, Handler h, NetworkCallback cb) {
- assertFalse(allCallbacks.containsKey(cb));
- allCallbacks.put(cb, h);
- assertFalse(requested.containsKey(cb));
- requested.put(cb, req);
- assertFalse(legacyTypeMap.containsKey(cb));
+ assertFalse(mAllCallbacks.containsKey(cb));
+ mAllCallbacks.put(cb, new NetworkRequestInfo(req, h));
+ assertFalse(mRequested.containsKey(cb));
+ mRequested.put(cb, new NetworkRequestInfo(req, h));
+ assertFalse(mLegacyTypeMap.containsKey(cb));
if (legacyType != ConnectivityManager.TYPE_NONE) {
- legacyTypeMap.put(cb, legacyType);
+ mLegacyTypeMap.put(cb, legacyType);
}
}
@Override
public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb, Handler h) {
- assertFalse(allCallbacks.containsKey(cb));
- allCallbacks.put(cb, h);
- assertFalse(listening.containsKey(cb));
- listening.put(cb, req);
+ assertFalse(mAllCallbacks.containsKey(cb));
+ mAllCallbacks.put(cb, new NetworkRequestInfo(req, h));
+ assertFalse(mListening.containsKey(cb));
+ mListening.put(cb, new NetworkRequestInfo(req, h));
}
@Override
@@ -176,57 +217,113 @@ public class TestConnectivityManager extends ConnectivityManager {
@Override
public void unregisterNetworkCallback(NetworkCallback cb) {
- if (trackingDefault.contains(cb)) {
- trackingDefault.remove(cb);
- } else if (listening.containsKey(cb)) {
- listening.remove(cb);
- } else if (requested.containsKey(cb)) {
- requested.remove(cb);
- legacyTypeMap.remove(cb);
+ if (mTrackingDefault.containsKey(cb)) {
+ mTrackingDefault.remove(cb);
+ } else if (mListening.containsKey(cb)) {
+ mListening.remove(cb);
+ } else if (mRequested.containsKey(cb)) {
+ mRequested.remove(cb);
+ mLegacyTypeMap.remove(cb);
} else {
fail("Unexpected callback removed");
}
- allCallbacks.remove(cb);
+ mAllCallbacks.remove(cb);
- assertFalse(allCallbacks.containsKey(cb));
- assertFalse(trackingDefault.contains(cb));
- assertFalse(listening.containsKey(cb));
- assertFalse(requested.containsKey(cb));
+ assertFalse(mAllCallbacks.containsKey(cb));
+ assertFalse(mTrackingDefault.containsKey(cb));
+ assertFalse(mListening.containsKey(cb));
+ assertFalse(mRequested.containsKey(cb));
+ }
+
+ private void sendConnectivityAction(int type, boolean connected) {
+ NetworkInfo ni = new NetworkInfo(type, 0 /* subtype */, getNetworkTypeName(type),
+ "" /* subtypeName */);
+ NetworkInfo.DetailedState state = connected
+ ? NetworkInfo.DetailedState.CONNECTED
+ : NetworkInfo.DetailedState.DISCONNECTED;
+ ni.setDetailedState(state, "" /* reason */, "" /* extraInfo */);
+ Intent intent = new Intent(CONNECTIVITY_ACTION);
+ intent.putExtra(EXTRA_NETWORK_INFO, ni);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
public static class TestNetworkAgent {
public final TestConnectivityManager cm;
public final Network networkId;
- public final int transportType;
public final NetworkCapabilities networkCapabilities;
public final LinkProperties linkProperties;
+ // TODO: delete when tethering no longer uses CONNECTIVITY_ACTION.
+ public final int legacyType;
- public TestNetworkAgent(TestConnectivityManager cm, int transportType) {
+ public TestNetworkAgent(TestConnectivityManager cm, NetworkCapabilities nc) {
this.cm = cm;
this.networkId = new Network(cm.getNetworkId());
- this.transportType = transportType;
- networkCapabilities = new NetworkCapabilities();
- networkCapabilities.addTransportType(transportType);
- networkCapabilities.addCapability(NET_CAPABILITY_INTERNET);
+ networkCapabilities = copy(nc);
linkProperties = new LinkProperties();
+ legacyType = toLegacyType(nc);
+ }
+
+ public TestNetworkAgent(TestConnectivityManager cm, UpstreamNetworkState state) {
+ this.cm = cm;
+ networkId = state.network;
+ networkCapabilities = state.networkCapabilities;
+ linkProperties = state.linkProperties;
+ this.legacyType = toLegacyType(networkCapabilities);
+ }
+
+ private static int toLegacyType(NetworkCapabilities nc) {
+ for (int type = 0; type < ConnectivityManager.TYPE_TEST; type++) {
+ if (matchesLegacyType(nc, type)) return type;
+ }
+ throw new IllegalArgumentException(("Can't determine legacy type for: ") + nc);
+ }
+
+ private static boolean matchesLegacyType(NetworkCapabilities nc, int legacyType) {
+ final NetworkCapabilities typeNc;
+ try {
+ typeNc = ConnectivityManager.networkCapabilitiesForType(legacyType);
+ } catch (IllegalArgumentException e) {
+ // networkCapabilitiesForType does not support all legacy types.
+ return false;
+ }
+ return typeNc.satisfiedByNetworkCapabilities(nc);
+ }
+
+ private boolean matchesLegacyType(int legacyType) {
+ return matchesLegacyType(networkCapabilities, legacyType);
}
public void fakeConnect() {
- for (NetworkCallback cb : cm.listening.keySet()) {
- cb.onAvailable(networkId);
- cb.onCapabilitiesChanged(networkId, copy(networkCapabilities));
- cb.onLinkPropertiesChanged(networkId, copy(linkProperties));
+ for (NetworkRequestInfo nri : cm.mRequested.values()) {
+ if (matchesLegacyType(nri.request.legacyType)) {
+ cm.sendConnectivityAction(legacyType, true /* connected */);
+ // In practice, a given network can match only one legacy type.
+ break;
+ }
+ }
+ for (NetworkCallback cb : cm.mListening.keySet()) {
+ final NetworkRequestInfo nri = cm.mListening.get(cb);
+ nri.handler.post(() -> cb.onAvailable(networkId));
+ nri.handler.post(() -> cb.onCapabilitiesChanged(
+ networkId, copy(networkCapabilities)));
+ nri.handler.post(() -> cb.onLinkPropertiesChanged(networkId, copy(linkProperties)));
}
}
public void fakeDisconnect() {
- for (NetworkCallback cb : cm.listening.keySet()) {
+ for (NetworkRequestInfo nri : cm.mRequested.values()) {
+ if (matchesLegacyType(nri.request.legacyType)) {
+ cm.sendConnectivityAction(legacyType, false /* connected */);
+ break;
+ }
+ }
+ for (NetworkCallback cb : cm.mListening.keySet()) {
cb.onLost(networkId);
}
}
public void sendLinkProperties() {
- for (NetworkCallback cb : cm.listening.keySet()) {
+ for (NetworkCallback cb : cm.mListening.keySet()) {
cb.onLinkPropertiesChanged(networkId, copy(linkProperties));
}
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 40b7c55f71..a7d61bd341 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -78,6 +78,7 @@ import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -165,6 +166,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.StateMachine;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent;
import com.android.testutils.MiscAsserts;
import org.junit.After;
@@ -174,6 +176,7 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -501,6 +504,7 @@ public class TetheringTest {
prop.setInterfaceName(interfaceName);
if (withIPv4) {
+ prop.addLinkAddress(new LinkAddress("10.1.2.3/15"));
prop.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
InetAddresses.parseNumericAddress("10.0.0.1"),
interfaceName, RTN_UNICAST));
@@ -614,6 +618,7 @@ public class TetheringTest {
when(mOffloadHardwareInterface.getForwardedStats(any())).thenReturn(mForwardedStats);
mServiceContext = new TestContext(mContext);
+ mServiceContext.setUseRegisteredHandlers(true);
mContentResolver = new MockContentResolver(mServiceContext);
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
setTetheringSupported(true /* supported */);
@@ -712,6 +717,7 @@ public class TetheringTest {
final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
intent.putExtra(EXTRA_WIFI_AP_STATE, state);
mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ mLooper.dispatchAll();
}
private void sendWifiApStateChanged(int state, String ifname, int ipmode) {
@@ -720,6 +726,7 @@ public class TetheringTest {
intent.putExtra(EXTRA_WIFI_AP_INTERFACE_NAME, ifname);
intent.putExtra(EXTRA_WIFI_AP_MODE, ipmode);
mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ mLooper.dispatchAll();
}
private static final String[] P2P_RECEIVER_PERMISSIONS_FOR_BROADCAST = {
@@ -746,6 +753,7 @@ public class TetheringTest {
mServiceContext.sendBroadcastAsUserMultiplePermissions(intent, UserHandle.ALL,
P2P_RECEIVER_PERMISSIONS_FOR_BROADCAST);
+ mLooper.dispatchAll();
}
private void sendUsbBroadcast(boolean connected, boolean configured, boolean function,
@@ -759,11 +767,13 @@ public class TetheringTest {
intent.putExtra(USB_FUNCTION_NCM, function);
}
mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ mLooper.dispatchAll();
}
private void sendConfigurationChanged() {
final Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED);
mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ mLooper.dispatchAll();
}
private void verifyDefaultNetworkRequestFiled() {
@@ -805,7 +815,6 @@ public class TetheringTest {
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
}
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED);
- mLooper.dispatchAll();
// If, and only if, Tethering received an interface status changed then
// it creates a IpServer and sends out a broadcast indicating that the
@@ -830,9 +839,7 @@ public class TetheringTest {
mTethering.interfaceStatusChanged(TEST_NCM_IFNAME, true);
}
- private void prepareUsbTethering(UpstreamNetworkState upstreamState) {
- initTetheringUpstream(upstreamState);
-
+ private void prepareUsbTethering() {
// Emulate pressing the USB tethering button in Settings UI.
final TetheringRequestParcel request = createTetheringRequestParcel(TETHERING_USB);
mTethering.startTethering(request, null);
@@ -847,14 +854,14 @@ public class TetheringTest {
@Test
public void testUsbConfiguredBroadcastStartsTethering() throws Exception {
UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
- prepareUsbTethering(upstreamState);
+ initTetheringUpstream(upstreamState);
+ prepareUsbTethering();
// This should produce no activity of any kind.
verifyNoMoreInteractions(mNetd);
// Pretend we then receive USB configured broadcast.
sendUsbBroadcast(true, true, true, TETHERING_USB);
- mLooper.dispatchAll();
// Now we should see the start of tethering mechanics (in this case:
// tetherMatchingInterfaces() which starts by fetching all interfaces).
verify(mNetd, times(1)).interfaceGetList();
@@ -883,7 +890,6 @@ public class TetheringTest {
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
}
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_LOCAL_ONLY);
- mLooper.dispatchAll();
verifyInterfaceServingModeStarted(TEST_WLAN_IFNAME);
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
@@ -942,9 +948,9 @@ public class TetheringTest {
}
private void runUsbTethering(UpstreamNetworkState upstreamState) {
- prepareUsbTethering(upstreamState);
+ initTetheringUpstream(upstreamState);
+ prepareUsbTethering();
sendUsbBroadcast(true, true, true, TETHERING_USB);
- mLooper.dispatchAll();
}
private void assertSetIfaceToDadProxy(final int numOfCalls, final String ifaceName) {
@@ -1083,10 +1089,75 @@ public class TetheringTest {
verify(mUpstreamNetworkMonitor, times(1)).setCurrentUpstream(upstreamState.network);
}
+ @Test
+ public void testAutomaticUpstreamSelection() throws Exception {
+ // Enable automatic upstream selection.
+ when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(true);
+ sendConfigurationChanged();
+ mLooper.dispatchAll();
+
+ InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
+
+ // Start USB tethering with no current upstream.
+ prepareUsbTethering();
+ sendUsbBroadcast(true, true, true, TETHERING_USB);
+ inOrder.verify(mUpstreamNetworkMonitor).startObserveAllNetworks();
+ inOrder.verify(mUpstreamNetworkMonitor).setTryCell(true);
+
+ // Pretend cellular connected and expect the upstream to be set.
+ TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
+ mobile.fakeConnect();
+ mCm.makeDefaultNetwork(mobile);
+ mLooper.dispatchAll();
+ inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId);
+
+ // Switch upstreams a few times.
+ // TODO: there may be a race where if the effects of the CONNECTIVITY_ACTION happen before
+ // UpstreamNetworkMonitor gets onCapabilitiesChanged on CALLBACK_DEFAULT_INTERNET, the
+ // upstream does not change. Extend TestConnectivityManager to simulate this condition and
+ // write a test for this.
+ TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
+ wifi.fakeConnect();
+ mCm.makeDefaultNetwork(wifi);
+ mLooper.dispatchAll();
+ inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(wifi.networkId);
+
+ mCm.makeDefaultNetwork(mobile);
+ mLooper.dispatchAll();
+ inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId);
+
+ // Wifi disconnecting should not have any affect since it's not the current upstream.
+ wifi.fakeDisconnect();
+ mLooper.dispatchAll();
+ inOrder.verify(mUpstreamNetworkMonitor, never()).setCurrentUpstream(any());
+
+ // Lose and regain upstream.
+ assertTrue(mUpstreamNetworkMonitor.getCurrentPreferredUpstream().linkProperties
+ .hasIPv4Address());
+ mobile.fakeDisconnect();
+ mCm.makeDefaultNetwork(null);
+ mLooper.dispatchAll();
+ inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(null);
+
+ mobile = new TestNetworkAgent(mCm, buildMobile464xlatUpstreamState());
+ mobile.fakeConnect();
+ mCm.makeDefaultNetwork(mobile);
+ mLooper.dispatchAll();
+ inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId);
+
+ // Check the IP addresses to ensure that the upstream is indeed not the same as the previous
+ // mobile upstream, even though the netId is (unrealistically) the same.
+ assertFalse(mUpstreamNetworkMonitor.getCurrentPreferredUpstream().linkProperties
+ .hasIPv4Address());
+ mobile.fakeDisconnect();
+ mCm.makeDefaultNetwork(null);
+ mLooper.dispatchAll();
+ inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(null);
+ }
+
private void runNcmTethering() {
prepareNcmTethering();
sendUsbBroadcast(true, true, true, TETHERING_NCM);
- mLooper.dispatchAll();
}
@Test
@@ -1134,7 +1205,6 @@ public class TetheringTest {
// tethering mode is to be started.
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED);
- mLooper.dispatchAll();
// There is 1 IpServer state change event: STATE_AVAILABLE
verify(mNotificationUpdater, times(1)).onDownstreamChanged(DOWNSTREAM_NONE);
@@ -1162,7 +1232,6 @@ public class TetheringTest {
// tethering mode is to be started.
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
- mLooper.dispatchAll();
verifyInterfaceServingModeStarted(TEST_WLAN_IFNAME);
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
@@ -1180,7 +1249,7 @@ public class TetheringTest {
verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
// In tethering mode, in the default configuration, an explicit request
// for a mobile network is also made.
- verify(mUpstreamNetworkMonitor, times(1)).registerMobileNetworkRequest();
+ verify(mUpstreamNetworkMonitor, times(1)).setTryCell(true);
// There are 2 IpServer state change events: STATE_AVAILABLE -> STATE_TETHERED
verify(mNotificationUpdater, times(1)).onDownstreamChanged(DOWNSTREAM_NONE);
verify(mNotificationUpdater, times(1)).onDownstreamChanged(eq(1 << TETHERING_WIFI));
@@ -1239,7 +1308,6 @@ public class TetheringTest {
// tethering mode is to be started.
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
- mLooper.dispatchAll();
// We verify get/set called three times here: twice for setup and once during
// teardown because all events happen over the course of the single
@@ -1563,7 +1631,6 @@ public class TetheringTest {
mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null);
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
- mLooper.dispatchAll();
tetherState = callback.pollTetherStatesChanged();
assertArrayEquals(tetherState.tetheredList, new String[] {TEST_WLAN_IFNAME});
callback.expectUpstreamChanged(upstreamState.network);
@@ -1585,7 +1652,6 @@ public class TetheringTest {
mLooper.dispatchAll();
mTethering.stopTethering(TETHERING_WIFI);
sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
- mLooper.dispatchAll();
tetherState = callback2.pollTetherStatesChanged();
assertArrayEquals(tetherState.availableList, new String[] {TEST_WLAN_IFNAME});
mLooper.dispatchAll();
@@ -1678,7 +1744,6 @@ public class TetheringTest {
mTethering.interfaceStatusChanged(TEST_P2P_IFNAME, true);
}
sendWifiP2pConnectionChanged(true, true, TEST_P2P_IFNAME);
- mLooper.dispatchAll();
verifyInterfaceServingModeStarted(TEST_P2P_IFNAME);
verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_AVAILABLE_TETHER);
@@ -1696,7 +1761,6 @@ public class TetheringTest {
// is being removed.
sendWifiP2pConnectionChanged(false, true, TEST_P2P_IFNAME);
mTethering.interfaceRemoved(TEST_P2P_IFNAME);
- mLooper.dispatchAll();
verify(mNetd, times(1)).tetherApplyDnsInterfaces();
verify(mNetd, times(1)).tetherInterfaceRemove(TEST_P2P_IFNAME);
@@ -1719,7 +1783,6 @@ public class TetheringTest {
mTethering.interfaceStatusChanged(TEST_P2P_IFNAME, true);
}
sendWifiP2pConnectionChanged(true, false, TEST_P2P_IFNAME);
- mLooper.dispatchAll();
verify(mNetd, never()).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
verify(mNetd, never()).tetherInterfaceAdd(TEST_P2P_IFNAME);
@@ -1731,7 +1794,6 @@ public class TetheringTest {
// is being removed.
sendWifiP2pConnectionChanged(false, false, TEST_P2P_IFNAME);
mTethering.interfaceRemoved(TEST_P2P_IFNAME);
- mLooper.dispatchAll();
verify(mNetd, never()).tetherApplyDnsInterfaces();
verify(mNetd, never()).tetherInterfaceRemove(TEST_P2P_IFNAME);
@@ -1767,7 +1829,6 @@ public class TetheringTest {
mTethering.interfaceStatusChanged(TEST_P2P_IFNAME, true);
}
sendWifiP2pConnectionChanged(true, true, TEST_P2P_IFNAME);
- mLooper.dispatchAll();
verify(mNetd, never()).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
verify(mNetd, never()).tetherInterfaceAdd(TEST_P2P_IFNAME);
@@ -1897,7 +1958,6 @@ public class TetheringTest {
// Expect that when USB comes up, the DHCP server is configured with the requested address.
mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true);
sendUsbBroadcast(true, true, true, TETHERING_USB);
- mLooper.dispatchAll();
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
verify(mNetd).interfaceSetCfg(argThat(cfg -> serverAddr.equals(cfg.ipv4Addr)));
@@ -1917,7 +1977,6 @@ public class TetheringTest {
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true);
sendUsbBroadcast(true, true, true, TETHERING_USB);
- mLooper.dispatchAll();
verify(mNetd).interfaceSetCfg(argThat(cfg -> serverAddr.equals(cfg.ipv4Addr)));
verify(mIpServerDependencies, times(1)).makeDhcpServer(any(), dhcpParamsCaptor.capture(),
any());
@@ -2141,7 +2200,6 @@ public class TetheringTest {
mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true);
sendUsbBroadcast(true, true, true, TETHERING_USB);
- mLooper.dispatchAll();
assertContains(Arrays.asList(mTethering.getTetherableIfaces()), TEST_USB_IFNAME);
assertContains(Arrays.asList(mTethering.getTetherableIfaces()), TEST_ETH_IFNAME);
assertEquals(TETHER_ERROR_IFACE_CFG_ERROR, mTethering.getLastTetherError(TEST_USB_IFNAME));
@@ -2180,7 +2238,6 @@ public class TetheringTest {
// Run local only tethering.
mTethering.interfaceStatusChanged(TEST_P2P_IFNAME, true);
sendWifiP2pConnectionChanged(true, true, TEST_P2P_IFNAME);
- mLooper.dispatchAll();
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
any(), dhcpEventCbsCaptor.capture());
eventCallbacks = dhcpEventCbsCaptor.getValue();
@@ -2197,7 +2254,6 @@ public class TetheringTest {
// Run wifi tethering.
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
- mLooper.dispatchAll();
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
any(), dhcpEventCbsCaptor.capture());
eventCallbacks = dhcpEventCbsCaptor.getValue();
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
index e358f5a2cd..bc216925d8 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
@@ -41,7 +41,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.IConnectivityManager;
import android.net.IpPrefix;
@@ -51,13 +50,16 @@ import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.util.SharedLog;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
+import android.os.test.TestLooper;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
+import com.android.networkstack.tethering.TestConnectivityManager.NetworkRequestInfo;
import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent;
import org.junit.After;
@@ -85,6 +87,13 @@ public class UpstreamNetworkMonitorTest {
// any specific TRANSPORT_* is sufficient to identify this request.
private static final NetworkRequest sDefaultRequest = new NetworkRequest.Builder().build();
+ private static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR).addCapability(NET_CAPABILITY_INTERNET).build();
+ private static final NetworkCapabilities DUN_CAPABILITIES = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR).addCapability(NET_CAPABILITY_DUN).build();
+ private static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI).addCapability(NET_CAPABILITY_INTERNET).build();
+
@Mock private Context mContext;
@Mock private EntitlementManager mEntitleMgr;
@Mock private IConnectivityManager mCS;
@@ -94,6 +103,8 @@ public class UpstreamNetworkMonitorTest {
private TestConnectivityManager mCM;
private UpstreamNetworkMonitor mUNM;
+ private final TestLooper mLooper = new TestLooper();
+
@Before public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
reset(mContext);
@@ -103,9 +114,8 @@ public class UpstreamNetworkMonitorTest {
when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
mCM = spy(new TestConnectivityManager(mContext, mCS, sDefaultRequest));
- mSM = new TestStateMachine();
- mUNM = new UpstreamNetworkMonitor(
- (ConnectivityManager) mCM, mSM, mLog, EVENT_UNM_UPDATE);
+ mSM = new TestStateMachine(mLooper.getLooper());
+ mUNM = new UpstreamNetworkMonitor(mCM, mSM, mLog, EVENT_UNM_UPDATE);
}
@After public void tearDown() throws Exception {
@@ -127,9 +137,9 @@ public class UpstreamNetworkMonitorTest {
assertTrue(mCM.hasNoCallbacks());
assertFalse(mUNM.mobileNetworkRequested());
- mUNM.updateMobileRequiresDun(true);
+ mUNM.setUpstreamConfig(false /* autoUpstream */, true /* dunRequired */);
assertTrue(mCM.hasNoCallbacks());
- mUNM.updateMobileRequiresDun(false);
+ mUNM.setUpstreamConfig(false /* autoUpstream */, false /* dunRequired */);
assertTrue(mCM.hasNoCallbacks());
}
@@ -139,7 +149,7 @@ public class UpstreamNetworkMonitorTest {
mUNM.startTrackDefaultNetwork(sDefaultRequest, mEntitleMgr);
mUNM.startObserveAllNetworks();
- assertEquals(1, mCM.trackingDefault.size());
+ assertEquals(1, mCM.mTrackingDefault.size());
mUNM.stop();
assertTrue(mCM.onlyHasDefaultCallbacks());
@@ -147,11 +157,11 @@ public class UpstreamNetworkMonitorTest {
@Test
public void testListensForAllNetworks() throws Exception {
- assertTrue(mCM.listening.isEmpty());
+ assertTrue(mCM.mListening.isEmpty());
mUNM.startTrackDefaultNetwork(sDefaultRequest, mEntitleMgr);
mUNM.startObserveAllNetworks();
- assertFalse(mCM.listening.isEmpty());
+ assertFalse(mCM.mListening.isEmpty());
assertTrue(mCM.isListeningForAll());
mUNM.stop();
@@ -174,17 +184,17 @@ public class UpstreamNetworkMonitorTest {
@Test
public void testRequestsMobileNetwork() throws Exception {
assertFalse(mUNM.mobileNetworkRequested());
- assertEquals(0, mCM.requested.size());
+ assertEquals(0, mCM.mRequested.size());
mUNM.startObserveAllNetworks();
assertFalse(mUNM.mobileNetworkRequested());
- assertEquals(0, mCM.requested.size());
+ assertEquals(0, mCM.mRequested.size());
- mUNM.updateMobileRequiresDun(false);
+ mUNM.setUpstreamConfig(false /* autoUpstream */, false /* dunRequired */);
assertFalse(mUNM.mobileNetworkRequested());
- assertEquals(0, mCM.requested.size());
+ assertEquals(0, mCM.mRequested.size());
- mUNM.registerMobileNetworkRequest();
+ mUNM.setTryCell(true);
assertTrue(mUNM.mobileNetworkRequested());
assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
assertFalse(isDunRequested());
@@ -197,16 +207,16 @@ public class UpstreamNetworkMonitorTest {
@Test
public void testDuplicateMobileRequestsIgnored() throws Exception {
assertFalse(mUNM.mobileNetworkRequested());
- assertEquals(0, mCM.requested.size());
+ assertEquals(0, mCM.mRequested.size());
mUNM.startObserveAllNetworks();
verify(mCM, times(1)).registerNetworkCallback(
any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
assertFalse(mUNM.mobileNetworkRequested());
- assertEquals(0, mCM.requested.size());
+ assertEquals(0, mCM.mRequested.size());
- mUNM.updateMobileRequiresDun(true);
- mUNM.registerMobileNetworkRequest();
+ mUNM.setUpstreamConfig(false /* autoUpstream */, true /* dunRequired */);
+ mUNM.setTryCell(true);
verify(mCM, times(1)).requestNetwork(
any(NetworkRequest.class), anyInt(), anyInt(), any(Handler.class),
any(NetworkCallback.class));
@@ -216,9 +226,9 @@ public class UpstreamNetworkMonitorTest {
assertTrue(isDunRequested());
// Try a few things that must not result in any state change.
- mUNM.registerMobileNetworkRequest();
- mUNM.updateMobileRequiresDun(true);
- mUNM.registerMobileNetworkRequest();
+ mUNM.setTryCell(true);
+ mUNM.setUpstreamConfig(false /* autoUpstream */, true /* dunRequired */);
+ mUNM.setTryCell(true);
assertTrue(mUNM.mobileNetworkRequested());
assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
@@ -233,17 +243,17 @@ public class UpstreamNetworkMonitorTest {
@Test
public void testRequestsDunNetwork() throws Exception {
assertFalse(mUNM.mobileNetworkRequested());
- assertEquals(0, mCM.requested.size());
+ assertEquals(0, mCM.mRequested.size());
mUNM.startObserveAllNetworks();
assertFalse(mUNM.mobileNetworkRequested());
- assertEquals(0, mCM.requested.size());
+ assertEquals(0, mCM.mRequested.size());
- mUNM.updateMobileRequiresDun(true);
+ mUNM.setUpstreamConfig(false /* autoUpstream */, true /* dunRequired */);
assertFalse(mUNM.mobileNetworkRequested());
- assertEquals(0, mCM.requested.size());
+ assertEquals(0, mCM.mRequested.size());
- mUNM.registerMobileNetworkRequest();
+ mUNM.setTryCell(true);
assertTrue(mUNM.mobileNetworkRequested());
assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
assertTrue(isDunRequested());
@@ -258,18 +268,18 @@ public class UpstreamNetworkMonitorTest {
mUNM.startObserveAllNetworks();
// Test going from no-DUN to DUN correctly re-registers callbacks.
- mUNM.updateMobileRequiresDun(false);
- mUNM.registerMobileNetworkRequest();
+ mUNM.setUpstreamConfig(false /* autoUpstream */, false /* dunRequired */);
+ mUNM.setTryCell(true);
assertTrue(mUNM.mobileNetworkRequested());
assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
assertFalse(isDunRequested());
- mUNM.updateMobileRequiresDun(true);
+ mUNM.setUpstreamConfig(false /* autoUpstream */, true /* dunRequired */);
assertTrue(mUNM.mobileNetworkRequested());
assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
assertTrue(isDunRequested());
// Test going from DUN to no-DUN correctly re-registers callbacks.
- mUNM.updateMobileRequiresDun(false);
+ mUNM.setUpstreamConfig(false /* autoUpstream */, false /* dunRequired */);
assertTrue(mUNM.mobileNetworkRequested());
assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
assertFalse(isDunRequested());
@@ -288,75 +298,80 @@ public class UpstreamNetworkMonitorTest {
// There are no networks, so there is nothing to select.
assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
- final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
+ final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, WIFI_CAPABILITIES);
wifiAgent.fakeConnect();
+ mLooper.dispatchAll();
// WiFi is up, we should prefer it.
assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
wifiAgent.fakeDisconnect();
+ mLooper.dispatchAll();
// There are no networks, so there is nothing to select.
assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
- final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
+ final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, CELL_CAPABILITIES);
cellAgent.fakeConnect();
+ mLooper.dispatchAll();
assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
preferredTypes.add(TYPE_MOBILE_DUN);
// This is coupled with preferred types in TetheringConfiguration.
- mUNM.updateMobileRequiresDun(true);
+ mUNM.setUpstreamConfig(false /* autoUpstream */, true /* dunRequired */);
// DUN is available, but only use regular cell: no upstream selected.
assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
preferredTypes.remove(TYPE_MOBILE_DUN);
// No WiFi, but our preferred flavour of cell is up.
preferredTypes.add(TYPE_MOBILE_HIPRI);
// This is coupled with preferred types in TetheringConfiguration.
- mUNM.updateMobileRequiresDun(false);
+ mUNM.setUpstreamConfig(false /* autoUpstream */, false /* dunRequired */);
assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI,
mUNM.selectPreferredUpstreamType(preferredTypes));
// Check to see we filed an explicit request.
- assertEquals(1, mCM.requested.size());
- NetworkRequest netReq = (NetworkRequest) mCM.requested.values().toArray()[0];
+ assertEquals(1, mCM.mRequested.size());
+ NetworkRequest netReq = ((NetworkRequestInfo) mCM.mRequested.values().toArray()[0]).request;
assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));
assertFalse(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
// mobile is not permitted, we should not use HIPRI.
when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false);
assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
- assertEquals(0, mCM.requested.size());
+ assertEquals(0, mCM.mRequested.size());
// mobile change back to permitted, HIRPI should come back
when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI,
mUNM.selectPreferredUpstreamType(preferredTypes));
wifiAgent.fakeConnect();
+ mLooper.dispatchAll();
// WiFi is up, and we should prefer it over cell.
assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
- assertEquals(0, mCM.requested.size());
+ assertEquals(0, mCM.mRequested.size());
preferredTypes.remove(TYPE_MOBILE_HIPRI);
preferredTypes.add(TYPE_MOBILE_DUN);
// This is coupled with preferred types in TetheringConfiguration.
- mUNM.updateMobileRequiresDun(true);
+ mUNM.setUpstreamConfig(false /* autoUpstream */, true /* dunRequired */);
assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
- final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
- dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
+ final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, DUN_CAPABILITIES);
dunAgent.fakeConnect();
+ mLooper.dispatchAll();
// WiFi is still preferred.
assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
// WiFi goes down, cell and DUN are still up but only DUN is preferred.
wifiAgent.fakeDisconnect();
+ mLooper.dispatchAll();
assertSatisfiesLegacyType(TYPE_MOBILE_DUN,
mUNM.selectPreferredUpstreamType(preferredTypes));
// Check to see we filed an explicit request.
- assertEquals(1, mCM.requested.size());
- netReq = (NetworkRequest) mCM.requested.values().toArray()[0];
+ assertEquals(1, mCM.mRequested.size());
+ netReq = ((NetworkRequestInfo) mCM.mRequested.values().toArray()[0]).request;
assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));
assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
// mobile is not permitted, we should not use DUN.
when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false);
assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
- assertEquals(0, mCM.requested.size());
+ assertEquals(0, mCM.mRequested.size());
// mobile change back to permitted, DUN should come back
when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
assertSatisfiesLegacyType(TYPE_MOBILE_DUN,
@@ -367,49 +382,72 @@ public class UpstreamNetworkMonitorTest {
public void testGetCurrentPreferredUpstream() throws Exception {
mUNM.startTrackDefaultNetwork(sDefaultRequest, mEntitleMgr);
mUNM.startObserveAllNetworks();
- mUNM.updateMobileRequiresDun(false);
+ mUNM.setUpstreamConfig(true /* autoUpstream */, false /* dunRequired */);
+ mUNM.setTryCell(true);
// [0] Mobile connects, DUN not required -> mobile selected.
- final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
+ final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, CELL_CAPABILITIES);
cellAgent.fakeConnect();
mCM.makeDefaultNetwork(cellAgent);
+ mLooper.dispatchAll();
assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
+ assertEquals(0, mCM.mRequested.size());
// [1] Mobile connects but not permitted -> null selected
when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false);
assertEquals(null, mUNM.getCurrentPreferredUpstream());
when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
+ assertEquals(0, mCM.mRequested.size());
// [2] WiFi connects but not validated/promoted to default -> mobile selected.
- final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
+ final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, WIFI_CAPABILITIES);
wifiAgent.fakeConnect();
+ mLooper.dispatchAll();
assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
+ assertEquals(0, mCM.mRequested.size());
// [3] WiFi validates and is promoted to the default network -> WiFi selected.
mCM.makeDefaultNetwork(wifiAgent);
+ mLooper.dispatchAll();
assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
+ assertEquals(0, mCM.mRequested.size());
// [4] DUN required, no other changes -> WiFi still selected
- mUNM.updateMobileRequiresDun(true);
+ mUNM.setUpstreamConfig(false /* autoUpstream */, true /* dunRequired */);
assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
+ assertEquals(1, mCM.mRequested.size());
+ assertTrue(isDunRequested());
// [5] WiFi no longer validated, mobile becomes default, DUN required -> null selected.
mCM.makeDefaultNetwork(cellAgent);
+ mLooper.dispatchAll();
assertEquals(null, mUNM.getCurrentPreferredUpstream());
- // TODO: make sure that a DUN request has been filed. This is currently
- // triggered by code over in Tethering, but once that has been moved
- // into UNM we should test for this here.
+ assertEquals(1, mCM.mRequested.size());
+ assertTrue(isDunRequested());
// [6] DUN network arrives -> DUN selected
- final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
+ final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, CELL_CAPABILITIES);
dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
dunAgent.networkCapabilities.removeCapability(NET_CAPABILITY_INTERNET);
dunAgent.fakeConnect();
+ mLooper.dispatchAll();
assertEquals(dunAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
+ assertEquals(1, mCM.mRequested.size());
// [7] Mobile is not permitted -> null selected
when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false);
assertEquals(null, mUNM.getCurrentPreferredUpstream());
+ assertEquals(1, mCM.mRequested.size());
+
+ // [7] Mobile is permitted again -> DUN selected
+ when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
+ assertEquals(dunAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
+ assertEquals(1, mCM.mRequested.size());
+
+ // [8] DUN no longer required -> request is withdrawn
+ mUNM.setUpstreamConfig(true /* autoUpstream */, false /* dunRequired */);
+ assertEquals(0, mCM.mRequested.size());
+ assertFalse(isDunRequested());
}
@Test
@@ -424,7 +462,7 @@ public class UpstreamNetworkMonitorTest {
final Set<String> alreadySeen = new HashSet<>();
// [1] Pretend Wi-Fi connects.
- final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
+ final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, WIFI_CAPABILITIES);
final LinkProperties wifiLp = wifiAgent.linkProperties;
wifiLp.setInterfaceName("wlan0");
final String[] wifi_addrs = {
@@ -439,6 +477,7 @@ public class UpstreamNetworkMonitorTest {
}
wifiAgent.fakeConnect();
wifiAgent.sendLinkProperties();
+ mLooper.dispatchAll();
local = mUNM.getLocalPrefixes();
assertPrefixSet(local, INCLUDES, alreadySeen);
@@ -451,7 +490,7 @@ public class UpstreamNetworkMonitorTest {
assertEquals(alreadySeen.size(), local.size());
// [2] Pretend mobile connects.
- final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
+ final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, CELL_CAPABILITIES);
final LinkProperties cellLp = cellAgent.linkProperties;
cellLp.setInterfaceName("rmnet_data0");
final String[] cell_addrs = {
@@ -463,6 +502,7 @@ public class UpstreamNetworkMonitorTest {
}
cellAgent.fakeConnect();
cellAgent.sendLinkProperties();
+ mLooper.dispatchAll();
local = mUNM.getLocalPrefixes();
assertPrefixSet(local, INCLUDES, alreadySeen);
@@ -472,9 +512,7 @@ public class UpstreamNetworkMonitorTest {
assertEquals(alreadySeen.size(), local.size());
// [3] Pretend DUN connects.
- final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
- dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
- dunAgent.networkCapabilities.removeCapability(NET_CAPABILITY_INTERNET);
+ final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, DUN_CAPABILITIES);
final LinkProperties dunLp = dunAgent.linkProperties;
dunLp.setInterfaceName("rmnet_data1");
final String[] dun_addrs = {
@@ -486,6 +524,7 @@ public class UpstreamNetworkMonitorTest {
}
dunAgent.fakeConnect();
dunAgent.sendLinkProperties();
+ mLooper.dispatchAll();
local = mUNM.getLocalPrefixes();
assertPrefixSet(local, INCLUDES, alreadySeen);
@@ -497,6 +536,7 @@ public class UpstreamNetworkMonitorTest {
// [4] Pretend Wi-Fi disconnected. It's addresses/prefixes should no
// longer be included (should be properly removed).
wifiAgent.fakeDisconnect();
+ mLooper.dispatchAll();
local = mUNM.getLocalPrefixes();
assertPrefixSet(local, EXCLUDES, wifiLinkPrefixes);
assertPrefixSet(local, INCLUDES, cellLinkPrefixes);
@@ -504,6 +544,7 @@ public class UpstreamNetworkMonitorTest {
// [5] Pretend mobile disconnected.
cellAgent.fakeDisconnect();
+ mLooper.dispatchAll();
local = mUNM.getLocalPrefixes();
assertPrefixSet(local, EXCLUDES, wifiLinkPrefixes);
assertPrefixSet(local, EXCLUDES, cellLinkPrefixes);
@@ -511,6 +552,7 @@ public class UpstreamNetworkMonitorTest {
// [6] Pretend DUN disconnected.
dunAgent.fakeDisconnect();
+ mLooper.dispatchAll();
local = mUNM.getLocalPrefixes();
assertTrue(local.isEmpty());
}
@@ -524,12 +566,13 @@ public class UpstreamNetworkMonitorTest {
mUNM.startTrackDefaultNetwork(sDefaultRequest, mEntitleMgr);
mUNM.startObserveAllNetworks();
// Setup wifi and make wifi as default network.
- final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
+ final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, WIFI_CAPABILITIES);
wifiAgent.fakeConnect();
mCM.makeDefaultNetwork(wifiAgent);
// Setup mobile network.
- final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
+ final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, CELL_CAPABILITIES);
cellAgent.fakeConnect();
+ mLooper.dispatchAll();
assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI,
mUNM.selectPreferredUpstreamType(preferredTypes));
@@ -548,15 +591,15 @@ public class UpstreamNetworkMonitorTest {
}
private void assertUpstreamTypeRequested(int upstreamType) throws Exception {
- assertEquals(1, mCM.requested.size());
- assertEquals(1, mCM.legacyTypeMap.size());
+ assertEquals(1, mCM.mRequested.size());
+ assertEquals(1, mCM.mLegacyTypeMap.size());
assertEquals(Integer.valueOf(upstreamType),
- mCM.legacyTypeMap.values().iterator().next());
+ mCM.mLegacyTypeMap.values().iterator().next());
}
private boolean isDunRequested() {
- for (NetworkRequest req : mCM.requested.values()) {
- if (req.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)) {
+ for (NetworkRequestInfo nri : mCM.mRequested.values()) {
+ if (nri.request.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)) {
return true;
}
}
@@ -582,8 +625,8 @@ public class UpstreamNetworkMonitorTest {
}
}
- public TestStateMachine() {
- super("UpstreamNetworkMonitor.TestStateMachine");
+ public TestStateMachine(Looper looper) {
+ super("UpstreamNetworkMonitor.TestStateMachine", looper);
addState(mLoggingState);
setInitialState(mLoggingState);
super.start();
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index f423503c56..48923b9acb 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -25,7 +25,8 @@ import static com.android.cts.net.hostside.NetworkPolicyTestUtils.executeShellCo
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getConnectivityManager;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getContext;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getInstrumentation;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getWifiManager;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isAppStandbySupported;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.restrictBackgroundValueToString;
@@ -46,12 +47,10 @@ import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
-import android.net.wifi.WifiManager;
import android.os.BatteryManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.SystemClock;
-import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.util.Log;
@@ -628,6 +627,9 @@ public abstract class AbstractRestrictBackgroundNetworkTestCase {
}
protected void setBatterySaverMode(boolean enabled) throws Exception {
+ if (!isBatterySaverSupported()) {
+ return;
+ }
Log.i(TAG, "Setting Battery Saver Mode to " + enabled);
if (enabled) {
turnBatteryOn();
@@ -639,8 +641,9 @@ public abstract class AbstractRestrictBackgroundNetworkTestCase {
}
protected void setDozeMode(boolean enabled) throws Exception {
- // Check doze mode is supported.
- assertTrue("Device does not support Doze Mode", isDozeModeSupported());
+ if (!isDozeModeSupported()) {
+ return;
+ }
Log.i(TAG, "Setting Doze Mode to " + enabled);
if (enabled) {
@@ -660,12 +663,18 @@ public abstract class AbstractRestrictBackgroundNetworkTestCase {
}
protected void setAppIdle(boolean enabled) throws Exception {
+ if (!isAppStandbySupported()) {
+ return;
+ }
Log.i(TAG, "Setting app idle to " + enabled);
executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
assertAppIdle(enabled);
}
protected void setAppIdleNoAssert(boolean enabled) throws Exception {
+ if (!isAppStandbySupported()) {
+ return;
+ }
Log.i(TAG, "Setting app idle to " + enabled);
executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java
index 5ecb399da0..66cb935a1c 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java
@@ -24,6 +24,8 @@ import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.util.Log;
+import androidx.test.platform.app.InstrumentationRegistry;
+
import com.android.compatibility.common.util.OnFailureRule;
import org.junit.AssumptionViolatedException;
@@ -37,23 +39,20 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
-import androidx.test.platform.app.InstrumentationRegistry;
-
public class DumpOnFailureRule extends OnFailureRule {
private File mDumpDir = new File(Environment.getExternalStorageDirectory(),
"CtsHostsideNetworkTests");
@Override
public void onTestFailure(Statement base, Description description, Throwable throwable) {
- final String testName = description.getClassName() + "_" + description.getMethodName();
-
if (throwable instanceof AssumptionViolatedException) {
+ final String testName = description.getClassName() + "_" + description.getMethodName();
Log.d(TAG, "Skipping test " + testName + ": " + throwable);
return;
}
prepareDumpRootDir();
- final File dumpFile = new File(mDumpDir, "dump-" + testName);
+ final File dumpFile = new File(mDumpDir, "dump-" + getShortenedTestName(description));
Log.i(TAG, "Dumping debug info for " + description + ": " + dumpFile.getPath());
try (FileOutputStream out = new FileOutputStream(dumpFile)) {
for (String cmd : new String[] {
@@ -71,6 +70,17 @@ public class DumpOnFailureRule extends OnFailureRule {
}
}
+ private String getShortenedTestName(Description description) {
+ final String qualifiedClassName = description.getClassName();
+ final String className = qualifiedClassName.substring(
+ qualifiedClassName.lastIndexOf(".") + 1);
+ final String shortenedClassName = className.chars()
+ .filter(Character::isUpperCase)
+ .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
+ .toString();
+ return shortenedClassName + "_" + description.getMethodName();
+ }
+
void dumpCommandOutput(FileOutputStream out, String cmd) {
final ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation()
.getUiAutomation().executeShellCommand(cmd);
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
index b61535bb54..0a134080ff 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
@@ -22,12 +22,13 @@ import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELI
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_METERED;
+import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_NONE;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -35,19 +36,21 @@ import static org.junit.Assert.fail;
import android.app.ActivityManager;
import android.app.Instrumentation;
+import android.app.UiAutomation;
import android.content.Context;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.ActionListener;
import android.os.PersistableBundle;
import android.os.Process;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.data.ApnSetting;
-import android.text.TextUtils;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -57,7 +60,12 @@ import com.android.compatibility.common.util.BatteryUtils;
import com.android.compatibility.common.util.ShellIdentityUtils;
import com.android.compatibility.common.util.ThrowingRunnable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
public class NetworkPolicyTestUtils {
@@ -98,11 +106,11 @@ public class NetworkPolicyTestUtils {
if (mDataSaverSupported == null) {
assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
try {
- setRestrictBackground(true);
+ setRestrictBackgroundInternal(true);
mDataSaverSupported = !isMyRestrictBackgroundStatus(
RESTRICT_BACKGROUND_STATUS_DISABLED);
} finally {
- setRestrictBackground(false);
+ setRestrictBackgroundInternal(false);
}
}
return mDataSaverSupported;
@@ -182,19 +190,14 @@ public class NetworkPolicyTestUtils {
}
private static String getWifiSsid() {
- final boolean isLocationEnabled = isLocationEnabled();
+ final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
try {
- if (!isLocationEnabled) {
- setLocationEnabled(true);
- }
- final String ssid = unquoteSSID(getWifiManager().getConnectionInfo().getSSID());
+ uiAutomation.adoptShellPermissionIdentity();
+ final String ssid = getWifiManager().getConnectionInfo().getSSID();
assertNotEquals(WifiManager.UNKNOWN_SSID, ssid);
return ssid;
} finally {
- // Reset the location enabled state
- if (!isLocationEnabled) {
- setLocationEnabled(false);
- }
+ uiAutomation.dropShellPermissionIdentity();
}
}
@@ -205,18 +208,71 @@ public class NetworkPolicyTestUtils {
}
private static void setWifiMeteredStatus(String ssid, boolean metered) throws Exception {
- assertFalse("SSID should not be empty", TextUtils.isEmpty(ssid));
- final String cmd = "cmd netpolicy set metered-network " + ssid + " " + metered;
- executeShellCommand(cmd);
- assertWifiMeteredStatus(ssid, metered);
- assertActiveNetworkMetered(metered);
+ final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ final WifiConfiguration currentConfig = getWifiConfiguration(ssid);
+ currentConfig.meteredOverride = metered
+ ? METERED_OVERRIDE_METERED : METERED_OVERRIDE_NONE;
+ BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();
+ getWifiManager().save(currentConfig, createActionListener(
+ blockingQueue, Integer.MAX_VALUE));
+ Integer resultCode = blockingQueue.poll(TIMEOUT_CHANGE_METEREDNESS_MS,
+ TimeUnit.MILLISECONDS);
+ if (resultCode == null) {
+ fail("Timed out waiting for meteredness to change; ssid=" + ssid
+ + ", metered=" + metered);
+ } else if (resultCode != Integer.MAX_VALUE) {
+ fail("Error overriding the meteredness; ssid=" + ssid
+ + ", metered=" + metered + ", error=" + resultCode);
+ }
+ final boolean success = assertActiveNetworkMetered(metered, false /* throwOnFailure */);
+ if (!success) {
+ Log.i(TAG, "Retry connecting to wifi; ssid=" + ssid);
+ blockingQueue = new LinkedBlockingQueue<>();
+ getWifiManager().connect(currentConfig, createActionListener(
+ blockingQueue, Integer.MAX_VALUE));
+ resultCode = blockingQueue.poll(TIMEOUT_CHANGE_METEREDNESS_MS,
+ TimeUnit.MILLISECONDS);
+ if (resultCode == null) {
+ fail("Timed out waiting for wifi to connect; ssid=" + ssid);
+ } else if (resultCode != Integer.MAX_VALUE) {
+ fail("Error connecting to wifi; ssid=" + ssid
+ + ", error=" + resultCode);
+ }
+ assertActiveNetworkMetered(metered, true /* throwOnFailure */);
+ }
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
}
- private static void assertWifiMeteredStatus(String ssid, boolean expectedMeteredStatus) {
- final String result = executeShellCommand("cmd netpolicy list wifi-networks");
- final String expectedLine = ssid + ";" + expectedMeteredStatus;
- assertTrue("Expected line: " + expectedLine + "; Actual result: " + result,
- result.contains(expectedLine));
+ private static WifiConfiguration getWifiConfiguration(String ssid) {
+ final List<String> ssids = new ArrayList<>();
+ for (WifiConfiguration config : getWifiManager().getConfiguredNetworks()) {
+ if (config.SSID.equals(ssid)) {
+ return config;
+ }
+ ssids.add(config.SSID);
+ }
+ fail("Couldn't find the wifi config; ssid=" + ssid
+ + ", all=" + Arrays.toString(ssids.toArray()));
+ return null;
+ }
+
+ private static ActionListener createActionListener(BlockingQueue<Integer> blockingQueue,
+ int successCode) {
+ return new ActionListener() {
+ @Override
+ public void onSuccess() {
+ blockingQueue.offer(successCode);
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ blockingQueue.offer(reason);
+ }
+ };
}
private static void setCellularMeteredStatus(int subId, boolean metered) throws Exception {
@@ -225,11 +281,11 @@ public class NetworkPolicyTestUtils {
new String[] {ApnSetting.TYPE_MMS_STRING});
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(getCarrierConfigManager(),
(cm) -> cm.overrideConfig(subId, metered ? null : bundle));
- assertActiveNetworkMetered(metered);
+ assertActiveNetworkMetered(metered, true /* throwOnFailure */);
}
- // Copied from cts/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
- private static void assertActiveNetworkMetered(boolean expectedMeteredStatus) throws Exception {
+ private static boolean assertActiveNetworkMetered(boolean expectedMeteredStatus,
+ boolean throwOnFailure) throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final NetworkCallback networkCallback = new NetworkCallback() {
@Override
@@ -246,16 +302,29 @@ public class NetworkPolicyTestUtils {
getConnectivityManager().registerDefaultNetworkCallback(networkCallback);
try {
if (!latch.await(TIMEOUT_CHANGE_METEREDNESS_MS, TimeUnit.MILLISECONDS)) {
- fail("Timed out waiting for active network metered status to change to "
- + expectedMeteredStatus + "; network = "
- + getConnectivityManager().getActiveNetwork());
+ final String errorMsg = "Timed out waiting for active network metered status "
+ + "to change to " + expectedMeteredStatus + "; network = "
+ + getConnectivityManager().getActiveNetwork();
+ if (throwOnFailure) {
+ fail(errorMsg);
+ }
+ Log.w(TAG, errorMsg);
+ return false;
}
+ return true;
} finally {
getConnectivityManager().unregisterNetworkCallback(networkCallback);
}
}
public static void setRestrictBackground(boolean enabled) {
+ if (!isDataSaverSupported()) {
+ return;
+ }
+ setRestrictBackgroundInternal(enabled);
+ }
+
+ private static void setRestrictBackgroundInternal(boolean enabled) {
executeShellCommand("cmd netpolicy set restrict-background " + enabled);
final String output = executeShellCommand("cmd netpolicy get restrict-background");
final String expectedSuffix = enabled ? "enabled" : "disabled";
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
index ce203795f9..37420bfb1c 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -152,8 +152,10 @@ abstract class HostsideNetworkTestCase extends DeviceTestCase implements IAbiRec
// build a meaningful error message
StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
for (Map.Entry<TestDescription, TestResult> resultEntry :
- result.getTestResults().entrySet()) {
- if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
+ result.getTestResults().entrySet()) {
+ final TestStatus testStatus = resultEntry.getValue().getStatus();
+ if (!TestStatus.PASSED.equals(testStatus)
+ && !TestStatus.ASSUMPTION_FAILURE.equals(testStatus)) {
errorBuilder.append(resultEntry.getKey().toString());
errorBuilder.append(":\n");
errorBuilder.append(resultEntry.getValue().getStackTrace());
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 7df8228e40..4a1020d962 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -96,7 +96,10 @@ android_test {
target_sdk_version: "30",
test_suites: [
"general-tests",
- "mts",
+ "mts-dnsresolver",
+ "mts-networking",
+ "mts-tethering",
+ "mts-wifi",
],
test_config_template: "AndroidTestTemplate.xml",
}
diff --git a/tests/cts/net/native/dns/Android.bp b/tests/cts/net/native/dns/Android.bp
index 1bc5a86068..5e9af8e55a 100644
--- a/tests/cts/net/native/dns/Android.bp
+++ b/tests/cts/net/native/dns/Android.bp
@@ -40,6 +40,7 @@ cc_test {
test_suites: [
"cts",
"general-tests",
- "mts",
+ "mts-dnsresolver",
+ "mts-networking",
],
}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index e3208e7f42..bfab497316 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -19,12 +19,29 @@ package android.net.cts;
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
import static android.content.pm.PackageManager.FEATURE_ETHERNET;
import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
import static android.content.pm.PackageManager.FEATURE_USB_HOST;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.content.pm.PackageManager.FEATURE_WIFI;
+import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_MOBILE_CBS;
+import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
+import static android.net.ConnectivityManager.TYPE_MOBILE_EMERGENCY;
+import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
+import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
+import static android.net.ConnectivityManager.TYPE_MOBILE_IA;
+import static android.net.ConnectivityManager.TYPE_MOBILE_IMS;
+import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
+import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
+import static android.net.ConnectivityManager.TYPE_PROXY;
+import static android.net.ConnectivityManager.TYPE_VPN;
+import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
@@ -78,7 +95,6 @@ import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
-import android.net.NetworkConfig;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
@@ -100,7 +116,9 @@ import android.os.SystemProperties;
import android.os.VintfRuntimeInfo;
import android.platform.test.annotations.AppModeFull;
import android.provider.Settings;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -115,6 +133,7 @@ import com.android.networkstack.apishim.common.ConnectivityManagerShim;
import com.android.testutils.CompatUtil;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRuleKt;
import com.android.testutils.RecorderCallback.CallbackEntry;
import com.android.testutils.SkipPresubmit;
import com.android.testutils.TestableNetworkCallback;
@@ -146,7 +165,6 @@ import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
@@ -201,8 +219,7 @@ public class ConnectivityManagerTest {
private ConnectivityManagerShim mCmShim;
private WifiManager mWifiManager;
private PackageManager mPackageManager;
- private final HashMap<Integer, NetworkConfig> mNetworks =
- new HashMap<Integer, NetworkConfig>();
+ private final ArraySet<Integer> mNetworkTypes = new ArraySet<>();
private UiAutomation mUiAutomation;
private CtsNetUtils mCtsNetUtils;
@@ -216,23 +233,67 @@ public class ConnectivityManagerTest {
mPackageManager = mContext.getPackageManager();
mCtsNetUtils = new CtsNetUtils(mContext);
+ if (DevSdkIgnoreRuleKt.isDevSdkInRange(null /* minExclusive */,
+ Build.VERSION_CODES.R /* maxInclusive */)) {
+ addLegacySupportedNetworkTypes();
+ } else {
+ addSupportedNetworkTypes();
+ }
+
+ mUiAutomation = mInstrumentation.getUiAutomation();
+
+ assertNotNull("CTS requires a working Internet connection", mCm.getActiveNetwork());
+ }
+
+ private void addLegacySupportedNetworkTypes() {
+ // Network type support as expected for android R-
// Get com.android.internal.R.array.networkAttributes
int resId = mContext.getResources().getIdentifier("networkAttributes", "array", "android");
String[] naStrings = mContext.getResources().getStringArray(resId);
- //TODO: What is the "correct" way to determine if this is a wifi only device?
boolean wifiOnly = SystemProperties.getBoolean("ro.radio.noril", false);
for (String naString : naStrings) {
try {
- NetworkConfig n = new NetworkConfig(naString);
- if (wifiOnly && ConnectivityManager.isNetworkTypeMobile(n.type)) {
+ final String[] splitConfig = naString.split(",");
+ // Format was name,type,radio,priority,restoreTime,dependencyMet
+ final int type = Integer.parseInt(splitConfig[1]);
+ if (wifiOnly && ConnectivityManager.isNetworkTypeMobile(type)) {
continue;
}
- mNetworks.put(n.type, n);
+ mNetworkTypes.add(type);
} catch (Exception e) {}
}
- mUiAutomation = mInstrumentation.getUiAutomation();
+ }
- assertNotNull("CTS requires a working Internet connection", mCm.getActiveNetwork());
+ private void addSupportedNetworkTypes() {
+ final PackageManager pm = mContext.getPackageManager();
+ if (pm.hasSystemFeature(FEATURE_WIFI)) {
+ mNetworkTypes.add(TYPE_WIFI);
+ }
+ if (pm.hasSystemFeature(FEATURE_WIFI_DIRECT)) {
+ mNetworkTypes.add(TYPE_WIFI_P2P);
+ }
+ if (mContext.getSystemService(TelephonyManager.class).isDataCapable()) {
+ mNetworkTypes.add(TYPE_MOBILE);
+ mNetworkTypes.add(TYPE_MOBILE_MMS);
+ mNetworkTypes.add(TYPE_MOBILE_SUPL);
+ mNetworkTypes.add(TYPE_MOBILE_DUN);
+ mNetworkTypes.add(TYPE_MOBILE_HIPRI);
+ mNetworkTypes.add(TYPE_MOBILE_FOTA);
+ mNetworkTypes.add(TYPE_MOBILE_IMS);
+ mNetworkTypes.add(TYPE_MOBILE_CBS);
+ mNetworkTypes.add(TYPE_MOBILE_IA);
+ mNetworkTypes.add(TYPE_MOBILE_EMERGENCY);
+ }
+ if (pm.hasSystemFeature(FEATURE_BLUETOOTH)) {
+ mNetworkTypes.add(TYPE_BLUETOOTH);
+ }
+ if (pm.hasSystemFeature(FEATURE_WATCH)) {
+ mNetworkTypes.add(TYPE_PROXY);
+ }
+ if (mContext.getSystemService(Context.ETHERNET_SERVICE) != null) {
+ mNetworkTypes.add(TYPE_ETHERNET);
+ }
+ mNetworkTypes.add(TYPE_VPN);
}
@After
@@ -461,9 +522,9 @@ public class ConnectivityManagerTest {
}
private boolean shouldBeSupported(int networkType) {
- return mNetworks.containsKey(networkType) ||
- (networkType == ConnectivityManager.TYPE_VPN) ||
- (networkType == ConnectivityManager.TYPE_ETHERNET && shouldEthernetBeSupported());
+ return mNetworkTypes.contains(networkType)
+ || (networkType == ConnectivityManager.TYPE_VPN)
+ || (networkType == ConnectivityManager.TYPE_ETHERNET && shouldEthernetBeSupported());
}
@Test
@@ -1603,15 +1664,16 @@ public class ConnectivityManagerTest {
// Verify background network cannot be requested without NETWORK_SETTINGS permission.
final TestableNetworkCallback callback = new TestableNetworkCallback();
+ final Handler handler = new Handler(Looper.getMainLooper());
assertThrows(SecurityException.class,
- () -> mCmShim.requestBackgroundNetwork(testRequest, null, callback));
+ () -> mCmShim.requestBackgroundNetwork(testRequest, handler, callback));
Network testNetwork = null;
try {
// Request background test network via Shell identity which has NETWORK_SETTINGS
// permission granted.
runWithShellPermissionIdentity(
- () -> mCmShim.requestBackgroundNetwork(testRequest, null, callback),
+ () -> mCmShim.requestBackgroundNetwork(testRequest, handler, callback),
new String[] { android.Manifest.permission.NETWORK_SETTINGS });
// Register the test network agent which has no foreground request associated to it.
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 827a05eae4..ef529f8289 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -18,6 +18,8 @@ package android.net.cts
import android.app.Instrumentation
import android.content.Context
import android.net.ConnectivityManager
+import android.net.INetworkAgent
+import android.net.INetworkAgentRegistry
import android.net.InetAddresses
import android.net.IpPrefix
import android.net.KeepalivePacketData
@@ -44,6 +46,7 @@ import android.net.NetworkCapabilities.TRANSPORT_VPN
import android.net.NetworkInfo
import android.net.NetworkProvider
import android.net.NetworkRequest
+import android.net.NetworkScore
import android.net.RouteInfo
import android.net.SocketKeepalive
import android.net.Uri
@@ -65,8 +68,6 @@ import android.os.Looper
import android.os.Message
import android.util.DebugUtils.valueToString
import androidx.test.InstrumentationRegistry
-import com.android.connectivity.aidl.INetworkAgent
-import com.android.connectivity.aidl.INetworkAgentRegistry
import com.android.modules.utils.build.SdkLevel
import com.android.net.module.util.ArrayTrackRecord
import com.android.testutils.CompatUtil
@@ -81,7 +82,6 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.argThat
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.doReturn
@@ -632,7 +632,7 @@ class NetworkAgentTest {
argThat<NetworkInfo> { it.detailedState == NetworkInfo.DetailedState.CONNECTING },
any(LinkProperties::class.java),
any(NetworkCapabilities::class.java),
- anyInt() /* score */,
+ any(NetworkScore::class.java),
any(NetworkAgentConfig::class.java),
eq(NetworkProvider.ID_NONE))
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index 30c4e72e10..9906c30ba7 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -39,17 +39,20 @@ import android.net.MatchAllNetworkSpecifier;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
-import android.net.UidRange;
import android.net.wifi.WifiNetworkSpecifier;
import android.os.Build;
import android.os.PatternMatcher;
import android.os.Process;
import android.util.ArraySet;
+import android.util.Range;
import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.build.SdkLevel;
import com.android.networkstack.apishim.ConstantsShim;
+import com.android.networkstack.apishim.NetworkRequestShimImpl;
+import com.android.networkstack.apishim.common.NetworkRequestShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -57,6 +60,8 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Set;
+
@RunWith(AndroidJUnit4.class)
public class NetworkRequestTest {
@Rule
@@ -225,6 +230,14 @@ public class NetworkRequestTest {
assertTrue(requestCellularInternet.canBeSatisfiedBy(capCellularVpnMmsInternet));
}
+ private void setUids(NetworkRequest.Builder builder, Set<Range<Integer>> ranges)
+ throws UnsupportedApiLevelException {
+ if (SdkLevel.isAtLeastS()) {
+ final NetworkRequestShim networkRequestShim = NetworkRequestShimImpl.newInstance();
+ networkRequestShim.setUids(builder, ranges);
+ }
+ }
+
@Test
@IgnoreUpTo(Build.VERSION_CODES.Q)
public void testInvariantInCanBeSatisfiedBy() {
@@ -232,15 +245,26 @@ public class NetworkRequestTest {
// NetworkCapabilities.satisfiedByNetworkCapabilities().
final LocalNetworkSpecifier specifier1 = new LocalNetworkSpecifier(1234 /* id */);
final int uid = Process.myUid();
- final ArraySet<UidRange> ranges = new ArraySet<>();
- ranges.add(new UidRange(uid, uid));
- final NetworkRequest requestCombination = new NetworkRequest.Builder()
+ final NetworkRequest.Builder nrBuilder = new NetworkRequest.Builder()
.addTransportType(TRANSPORT_CELLULAR)
.addCapability(NET_CAPABILITY_INTERNET)
.setLinkUpstreamBandwidthKbps(1000)
.setNetworkSpecifier(specifier1)
- .setSignalStrength(-123)
- .setUids(ranges).build();
+ .setSignalStrength(-123);
+
+ // The uid ranges should be set into the request, but setUids() takes a set of UidRange
+ // that is hidden and inaccessible from shims. Before, S setUids will be a no-op. But
+ // because NetworkRequest.Builder sets the UID of the request to the current UID, the
+ // request contains the current UID both on S and before S.
+ final Set<Range<Integer>> ranges = new ArraySet<>();
+ ranges.add(new Range<Integer>(uid, uid));
+ try {
+ setUids(nrBuilder, ranges);
+ } catch (UnsupportedApiLevelException e) {
+ // Not supported before API31.
+ }
+ final NetworkRequest requestCombination = nrBuilder.build();
+
final NetworkCapabilities capCell = new NetworkCapabilities.Builder()
.addTransportType(TRANSPORT_CELLULAR).build();
assertCorrectlySatisfies(false, requestCombination, capCell);
diff --git a/tests/cts/net/src/android/net/cts/VpnServiceTest.java b/tests/cts/net/src/android/net/cts/VpnServiceTest.java
index 15af23c6f8..5c7b5ca082 100644
--- a/tests/cts/net/src/android/net/cts/VpnServiceTest.java
+++ b/tests/cts/net/src/android/net/cts/VpnServiceTest.java
@@ -47,6 +47,7 @@ public class VpnServiceTest extends AndroidTestCase {
assertEquals(1, count);
}
+ @AppModeFull(reason = "establish() requires prepare(), which requires PackageManager access")
public void testEstablish() throws Exception {
ParcelFileDescriptor descriptor = null;
try {
@@ -62,7 +63,7 @@ public class VpnServiceTest extends AndroidTestCase {
}
}
- @AppModeFull(reason = "Socket cannot bind in instant app mode")
+ @AppModeFull(reason = "Protecting sockets requires prepare(), which requires PackageManager")
public void testProtect_DatagramSocket() throws Exception {
DatagramSocket socket = new DatagramSocket();
try {
@@ -77,6 +78,7 @@ public class VpnServiceTest extends AndroidTestCase {
}
}
+ @AppModeFull(reason = "Protecting sockets requires prepare(), which requires PackageManager")
public void testProtect_Socket() throws Exception {
Socket socket = new Socket();
try {
@@ -91,7 +93,7 @@ public class VpnServiceTest extends AndroidTestCase {
}
}
- @AppModeFull(reason = "Socket cannot bind in instant app mode")
+ @AppModeFull(reason = "Protecting sockets requires prepare(), which requires PackageManager")
public void testProtect_int() throws Exception {
DatagramSocket socket = new DatagramSocket();
ParcelFileDescriptor descriptor = ParcelFileDescriptor.fromDatagramSocket(socket);
diff --git a/tests/cts/tethering/Android.bp b/tests/cts/tethering/Android.bp
index 824c874330..fa52e9bb0b 100644
--- a/tests/cts/tethering/Android.bp
+++ b/tests/cts/tethering/Android.bp
@@ -52,7 +52,7 @@ android_test {
test_suites: [
"cts",
"general-tests",
- "mts",
+ "mts-tethering",
],
// Include both the 32 and 64 bit versions