Merge "Skip NetworkAgentTest via its runner on R-"
diff --git a/OWNERS b/OWNERS
index 0e1e65d..22b5561 100644
--- a/OWNERS
+++ b/OWNERS
@@ -2,5 +2,6 @@
jchalard@google.com
junyulai@google.com
lorenzo@google.com
+maze@google.com
reminv@google.com
satk@google.com
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 7427481..73175580 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -62,7 +62,10 @@
"com.android.tethering",
],
min_sdk_version: "30",
- header_libs: ["bpf_syscall_wrappers"],
+ header_libs: [
+ "bpf_syscall_wrappers",
+ "bpf_tethering_headers",
+ ],
srcs: [
"jni/*.cpp",
],
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 eafa3ea..4e615a1 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
@@ -17,6 +17,7 @@
package com.android.networkstack.tethering.apishim.api30;
import android.net.INetd;
+import android.net.MacAddress;
import android.net.TetherStatsParcel;
import android.net.util.SharedLog;
import android.os.RemoteException;
@@ -28,6 +29,8 @@
import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
+import com.android.networkstack.tethering.Tether4Key;
+import com.android.networkstack.tethering.Tether4Value;
import com.android.networkstack.tethering.TetherStatsValue;
/**
@@ -76,6 +79,17 @@
}
@Override
+ public boolean startUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex,
+ MacAddress srcMac, MacAddress dstMac, int mtu) {
+ return true;
+ }
+
+ @Override
+ public boolean stopUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex) {
+ return true;
+ }
+
+ @Override
@Nullable
public SparseArray<TetherStatsValue> tetherOffloadGetStats() {
final TetherStatsParcel[] tetherStatsList;
@@ -132,6 +146,19 @@
}
@Override
+ public boolean tetherOffloadRuleAdd(boolean downstream, @NonNull Tether4Key key,
+ @NonNull Tether4Value value) {
+ /* no op */
+ return true;
+ }
+
+ @Override
+ public boolean tetherOffloadRuleRemove(boolean downstream, @NonNull Tether4Key key) {
+ /* 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 4ebf914..4dc1c51 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
@@ -18,6 +18,7 @@
import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
+import android.net.MacAddress;
import android.net.util.SharedLog;
import android.system.ErrnoException;
import android.system.Os;
@@ -30,12 +31,15 @@
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.TetherIngressKey;
-import com.android.networkstack.tethering.TetherIngressValue;
+import com.android.networkstack.tethering.Tether4Key;
+import com.android.networkstack.tethering.Tether4Value;
+import com.android.networkstack.tethering.Tether6Value;
+import com.android.networkstack.tethering.TetherDownstream6Key;
import com.android.networkstack.tethering.TetherLimitKey;
import com.android.networkstack.tethering.TetherLimitValue;
import com.android.networkstack.tethering.TetherStatsKey;
import com.android.networkstack.tethering.TetherStatsValue;
+import com.android.networkstack.tethering.TetherUpstream6Key;
import java.io.FileDescriptor;
@@ -54,10 +58,21 @@
@NonNull
private final SharedLog mLog;
- // BPF map of ingress queueing discipline which pre-processes the packets by the IPv6
- // forwarding rules.
+ // BPF map for downstream IPv4 forwarding.
@Nullable
- private final BpfMap<TetherIngressKey, TetherIngressValue> mBpfIngressMap;
+ private final BpfMap<Tether4Key, Tether4Value> mBpfDownstream4Map;
+
+ // BPF map for upstream IPv4 forwarding.
+ @Nullable
+ private final BpfMap<Tether4Key, Tether4Value> mBpfUpstream4Map;
+
+ // BPF map for downstream IPv6 forwarding.
+ @Nullable
+ private final BpfMap<TetherDownstream6Key, Tether6Value> mBpfDownstream6Map;
+
+ // BPF map for upstream IPv6 forwarding.
+ @Nullable
+ private final BpfMap<TetherUpstream6Key, Tether6Value> mBpfUpstream6Map;
// BPF map of tethering statistics of the upstream interface since tethering startup.
@Nullable
@@ -69,25 +84,29 @@
public BpfCoordinatorShimImpl(@NonNull final Dependencies deps) {
mLog = deps.getSharedLog().forSubComponent(TAG);
- mBpfIngressMap = deps.getBpfIngressMap();
+ mBpfDownstream4Map = deps.getBpfDownstream4Map();
+ mBpfUpstream4Map = deps.getBpfUpstream4Map();
+ mBpfDownstream6Map = deps.getBpfDownstream6Map();
+ mBpfUpstream6Map = deps.getBpfUpstream6Map();
mBpfStatsMap = deps.getBpfStatsMap();
mBpfLimitMap = deps.getBpfLimitMap();
}
@Override
public boolean isInitialized() {
- return mBpfIngressMap != null && mBpfStatsMap != null && mBpfLimitMap != null;
+ return mBpfDownstream4Map != null && mBpfUpstream4Map != null && mBpfDownstream6Map != null
+ && mBpfUpstream6Map != null && mBpfStatsMap != null && mBpfLimitMap != null;
}
@Override
public boolean tetherOffloadRuleAdd(@NonNull final Ipv6ForwardingRule rule) {
if (!isInitialized()) return false;
- final TetherIngressKey key = rule.makeTetherIngressKey();
- final TetherIngressValue value = rule.makeTetherIngressValue();
+ final TetherDownstream6Key key = rule.makeTetherDownstream6Key();
+ final Tether6Value value = rule.makeTether6Value();
try {
- mBpfIngressMap.updateEntry(key, value);
+ mBpfDownstream6Map.updateEntry(key, value);
} catch (ErrnoException e) {
mLog.e("Could not update entry: ", e);
return false;
@@ -101,7 +120,7 @@
if (!isInitialized()) return false;
try {
- mBpfIngressMap.deleteEntry(rule.makeTetherIngressKey());
+ mBpfDownstream6Map.deleteEntry(rule.makeTetherDownstream6Key());
} catch (ErrnoException e) {
// Silent if the rule did not exist.
if (e.errno != OsConstants.ENOENT) {
@@ -113,6 +132,37 @@
}
@Override
+ public boolean startUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex,
+ MacAddress srcMac, MacAddress dstMac, int mtu) {
+ if (!isInitialized()) return false;
+
+ final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfindex);
+ final Tether6Value value = new Tether6Value(upstreamIfindex, srcMac,
+ dstMac, OsConstants.ETH_P_IPV6, mtu);
+ try {
+ mBpfUpstream6Map.insertEntry(key, value);
+ } catch (ErrnoException | IllegalStateException e) {
+ mLog.e("Could not insert upstream6 entry: " + e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean stopUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex) {
+ if (!isInitialized()) return false;
+
+ final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfindex);
+ try {
+ mBpfUpstream6Map.deleteEntry(key);
+ } catch (ErrnoException e) {
+ mLog.e("Could not delete upstream IPv6 entry: " + e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
@Nullable
public SparseArray<TetherStatsValue> tetherOffloadGetStats() {
if (!isInitialized()) return null;
@@ -233,14 +283,61 @@
}
@Override
+ public boolean tetherOffloadRuleAdd(boolean downstream, @NonNull Tether4Key key,
+ @NonNull Tether4Value value) {
+ if (!isInitialized()) return false;
+
+ try {
+ // The last used time field of the value is updated by the bpf program. Adding the same
+ // map pair twice causes the unexpected refresh. Must be fixed before starting the
+ // conntrack timeout extension implementation.
+ // TODO: consider using insertEntry.
+ if (downstream) {
+ mBpfDownstream4Map.updateEntry(key, value);
+ } else {
+ mBpfUpstream4Map.updateEntry(key, value);
+ }
+ } catch (ErrnoException e) {
+ mLog.e("Could not update entry: ", e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean tetherOffloadRuleRemove(boolean downstream, @NonNull Tether4Key key) {
+ if (!isInitialized()) return false;
+
+ try {
+ if (downstream) {
+ mBpfDownstream4Map.deleteEntry(key);
+ } else {
+ mBpfUpstream4Map.deleteEntry(key);
+ }
+ } catch (ErrnoException e) {
+ // Silent if the rule did not exist.
+ if (e.errno != OsConstants.ENOENT) {
+ mLog.e("Could not delete entry: ", e);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private String mapStatus(BpfMap m, String name) {
+ return name + "{" + (m != null ? "OK" : "ERROR") + "}";
+ }
+
+ @Override
public String toString() {
- return "mBpfIngressMap{"
- + (mBpfIngressMap != null ? "initialized" : "not initialized") + "}, "
- + "mBpfStatsMap{"
- + (mBpfStatsMap != null ? "initialized" : "not initialized") + "}, "
- + "mBpfLimitMap{"
- + (mBpfLimitMap != null ? "initialized" : "not initialized") + "} "
- + "}";
+ return String.join(", ", new String[] {
+ mapStatus(mBpfDownstream6Map, "mBpfDownstream6Map"),
+ mapStatus(mBpfUpstream6Map, "mBpfUpstream6Map"),
+ mapStatus(mBpfDownstream4Map, "mBpfDownstream4Map"),
+ mapStatus(mBpfUpstream4Map, "mBpfUpstream4Map"),
+ mapStatus(mBpfStatsMap, "mBpfStatsMap"),
+ mapStatus(mBpfLimitMap, "mBpfLimitMap")
+ });
}
/**
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 61abfa3..c61c449 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
@@ -16,6 +16,7 @@
package com.android.networkstack.tethering.apishim.common;
+import android.net.MacAddress;
import android.util.SparseArray;
import androidx.annotation.NonNull;
@@ -23,6 +24,8 @@
import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
+import com.android.networkstack.tethering.Tether4Key;
+import com.android.networkstack.tethering.Tether4Value;
import com.android.networkstack.tethering.TetherStatsValue;
/**
@@ -71,6 +74,27 @@
public abstract boolean tetherOffloadRuleRemove(@NonNull Ipv6ForwardingRule rule);
/**
+ * Starts IPv6 forwarding between the specified interfaces.
+
+ * @param downstreamIfindex the downstream interface index
+ * @param upstreamIfindex the upstream interface index
+ * @param srcMac the source MAC address to use for packets
+ * @oaram dstMac the destination MAC address to use for packets
+ * @return true if operation succeeded or was a no-op, false otherwise
+ */
+ public abstract boolean startUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex,
+ MacAddress srcMac, MacAddress dstMac, int mtu);
+
+ /**
+ * Stops IPv6 forwarding between the specified interfaces.
+
+ * @param downstreamIfindex the downstream interface index
+ * @param upstreamIfindex the upstream interface index
+ * @return true if operation succeeded or was a no-op, false otherwise
+ */
+ public abstract boolean stopUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex);
+
+ /**
* Return BPF tethering offload statistics.
*
* @return an array of TetherStatsValue's, where each entry contains the upstream interface
@@ -108,5 +132,16 @@
*/
@Nullable
public abstract TetherStatsValue tetherOffloadGetAndClearStats(int ifIndex);
+
+ /**
+ * Adds a tethering IPv4 offload rule to appropriate BPF map.
+ */
+ public abstract boolean tetherOffloadRuleAdd(boolean downstream, @NonNull Tether4Key key,
+ @NonNull Tether4Value value);
+
+ /**
+ * Deletes a tethering IPv4 offload rule from the appropriate BPF map.
+ */
+ public abstract boolean tetherOffloadRuleRemove(boolean downstream, @NonNull Tether4Key key);
}
diff --git a/Tethering/bpf_progs/Android.bp b/Tethering/bpf_progs/Android.bp
index d62e9a1..b06528b 100644
--- a/Tethering/bpf_progs/Android.bp
+++ b/Tethering/bpf_progs/Android.bp
@@ -15,6 +15,26 @@
//
//
+// struct definitions shared with JNI
+//
+cc_library_headers {
+ name: "bpf_tethering_headers",
+ vendor_available: false,
+ host_supported: false,
+ export_include_dirs: ["."],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ sdk_version: "30",
+ min_sdk_version: "30",
+ apex_available: ["com.android.tethering"],
+ visibility: [
+ "//packages/modules/Connectivity/Tethering",
+ ],
+}
+
+//
// bpf kernel programs
//
bpf {
diff --git a/Tethering/bpf_progs/bpf_tethering.h b/Tethering/bpf_progs/bpf_tethering.h
new file mode 100644
index 0000000..10e6bb0
--- /dev/null
+++ b/Tethering/bpf_progs/bpf_tethering.h
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+// Common definitions for BPF code in the tethering mainline module.
+// These definitions are available to:
+// - The BPF programs in Tethering/bpf_progs/
+// - JNI code that depends on the bpf_tethering_headers library.
+
+#define BPF_TETHER_ERRORS \
+ ERR(INVALID_IP_VERSION) \
+ ERR(LOW_TTL) \
+ ERR(INVALID_TCP_HEADER) \
+ ERR(TCP_CONTROL_PACKET) \
+ ERR(NON_GLOBAL_SRC) \
+ ERR(NON_GLOBAL_DST) \
+ ERR(LOCAL_SRC_DST) \
+ ERR(NO_STATS_ENTRY) \
+ ERR(NO_LIMIT_ENTRY) \
+ ERR(BELOW_IPV4_MTU) \
+ ERR(BELOW_IPV6_MTU) \
+ ERR(LIMIT_REACHED) \
+ ERR(CHANGE_HEAD_FAILED) \
+ ERR(TOO_SHORT) \
+ ERR(HAS_IP_OPTIONS) \
+ ERR(IS_IP_FRAG) \
+ ERR(CHECKSUM) \
+ ERR(NON_TCP_UDP) \
+ ERR(NON_TCP) \
+ ERR(SHORT_TCP_HEADER) \
+ ERR(SHORT_UDP_HEADER) \
+ ERR(TRUNCATED_IPV4) \
+ ERR(_MAX)
+
+#define ERR(x) BPF_TETHER_ERR_ ##x,
+enum {
+ BPF_TETHER_ERRORS
+};
+#undef ERR
+
+#define ERR(x) #x,
+static const char *bpf_tether_errors[] = {
+ BPF_TETHER_ERRORS
+};
+#undef ERR
diff --git a/Tethering/bpf_progs/offload.c b/Tethering/bpf_progs/offload.c
index cc5af31..d4b5246 100644
--- a/Tethering/bpf_progs/offload.c
+++ b/Tethering/bpf_progs/offload.c
@@ -20,27 +20,98 @@
#include <linux/pkt_cls.h>
#include <linux/tcp.h>
+// bionic kernel uapi linux/udp.h header is munged...
+#define __kernel_udphdr udphdr
+#include <linux/udp.h>
+
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
+#include "bpf_tethering.h"
#include "netdbpf/bpf_shared.h"
-DEFINE_BPF_MAP_GRW(tether_ingress_map, HASH, TetherIngressKey, TetherIngressValue, 64,
- AID_NETWORK_STACK)
+// From kernel:include/net/ip.h
+#define IP_DF 0x4000 // Flag: "Don't Fragment"
+
+// ----- Helper functions for offsets to fields -----
+
+// They all assume simple IP packets:
+// - no VLAN ethernet tags
+// - no IPv4 options (see IPV4_HLEN/TCP4_OFFSET/UDP4_OFFSET)
+// - no IPv6 extension headers
+// - no TCP options (see TCP_HLEN)
+
+//#define ETH_HLEN sizeof(struct ethhdr)
+#define IP4_HLEN sizeof(struct iphdr)
+#define IP6_HLEN sizeof(struct ipv6hdr)
+#define TCP_HLEN sizeof(struct tcphdr)
+#define UDP_HLEN sizeof(struct udphdr)
+
+// Offsets from beginning of L4 (TCP/UDP) header
+#define TCP_OFFSET(field) offsetof(struct tcphdr, field)
+#define UDP_OFFSET(field) offsetof(struct udphdr, field)
+
+// Offsets from beginning of L3 (IPv4) header
+#define IP4_OFFSET(field) offsetof(struct iphdr, field)
+#define IP4_TCP_OFFSET(field) (IP4_HLEN + TCP_OFFSET(field))
+#define IP4_UDP_OFFSET(field) (IP4_HLEN + UDP_OFFSET(field))
+
+// Offsets from beginning of L3 (IPv6) header
+#define IP6_OFFSET(field) offsetof(struct ipv6hdr, field)
+#define IP6_TCP_OFFSET(field) (IP6_HLEN + TCP_OFFSET(field))
+#define IP6_UDP_OFFSET(field) (IP6_HLEN + UDP_OFFSET(field))
+
+// Offsets from beginning of L2 (ie. Ethernet) header (which must be present)
+#define ETH_IP4_OFFSET(field) (ETH_HLEN + IP4_OFFSET(field))
+#define ETH_IP4_TCP_OFFSET(field) (ETH_HLEN + IP4_TCP_OFFSET(field))
+#define ETH_IP4_UDP_OFFSET(field) (ETH_HLEN + IP4_UDP_OFFSET(field))
+#define ETH_IP6_OFFSET(field) (ETH_HLEN + IP6_OFFSET(field))
+#define ETH_IP6_TCP_OFFSET(field) (ETH_HLEN + IP6_TCP_OFFSET(field))
+#define ETH_IP6_UDP_OFFSET(field) (ETH_HLEN + IP6_UDP_OFFSET(field))
+
+// ----- Tethering stats and data limits -----
// Tethering stats, indexed by upstream interface.
-DEFINE_BPF_MAP_GRW(tether_stats_map, HASH, uint32_t, TetherStatsValue, 16, AID_NETWORK_STACK)
+DEFINE_BPF_MAP_GRW(tether_stats_map, HASH, TetherStatsKey, TetherStatsValue, 16, AID_NETWORK_STACK)
// Tethering data limit, indexed by upstream interface.
// (tethering allowed when stats[iif].rxBytes + stats[iif].txBytes < limit[iif])
-DEFINE_BPF_MAP_GRW(tether_limit_map, HASH, uint32_t, uint64_t, 16, AID_NETWORK_STACK)
+DEFINE_BPF_MAP_GRW(tether_limit_map, HASH, TetherLimitKey, TetherLimitValue, 16, AID_NETWORK_STACK)
-static inline __always_inline int do_forward(struct __sk_buff* skb, bool is_ethernet) {
- int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
+// ----- IPv6 Support -----
+
+DEFINE_BPF_MAP_GRW(tether_downstream6_map, HASH, TetherDownstream6Key, Tether6Value, 64,
+ AID_NETWORK_STACK)
+
+DEFINE_BPF_MAP_GRW(tether_downstream64_map, HASH, TetherDownstream64Key, TetherDownstream64Value,
+ 64, AID_NETWORK_STACK)
+
+DEFINE_BPF_MAP_GRW(tether_upstream6_map, HASH, TetherUpstream6Key, Tether6Value, 64,
+ AID_NETWORK_STACK)
+
+DEFINE_BPF_MAP_GRW(tether_error_map, ARRAY, __u32, __u32, BPF_TETHER_ERR__MAX,
+ AID_NETWORK_STACK)
+
+#define COUNT_AND_RETURN(counter, ret) do { \
+ __u32 code = BPF_TETHER_ERR_ ## counter; \
+ __u32 *count = bpf_tether_error_map_lookup_elem(&code); \
+ if (count) __sync_fetch_and_add(count, 1); \
+ return ret; \
+} while(0)
+
+#define DROP(counter) COUNT_AND_RETURN(counter, TC_ACT_SHOT)
+#define PUNT(counter) COUNT_AND_RETURN(counter, TC_ACT_OK)
+
+static inline __always_inline int do_forward6(struct __sk_buff* skb, const bool is_ethernet,
+ const bool downstream) {
+ const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
void* data = (void*)(long)skb->data;
const void* data_end = (void*)(long)skb->data_end;
struct ethhdr* eth = is_ethernet ? data : NULL; // used iff is_ethernet
struct ipv6hdr* ip6 = is_ethernet ? (void*)(eth + 1) : data;
+ // Require ethernet dst mac address to be our unicast address.
+ if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_OK;
+
// Must be meta-ethernet IPv6 frame
if (skb->protocol != htons(ETH_P_IPV6)) return TC_ACT_OK;
@@ -51,43 +122,70 @@
if (is_ethernet && (eth->h_proto != htons(ETH_P_IPV6))) return TC_ACT_OK;
// IP version must be 6
- if (ip6->version != 6) return TC_ACT_OK;
+ if (ip6->version != 6) PUNT(INVALID_IP_VERSION);
// Cannot decrement during forward if already zero or would be zero,
// Let the kernel's stack handle these cases and generate appropriate ICMP errors.
- if (ip6->hop_limit <= 1) return TC_ACT_OK;
+ if (ip6->hop_limit <= 1) PUNT(LOW_TTL);
+
+ // If hardware offload is running and programming flows based on conntrack entries,
+ // try not to interfere with it.
+ if (ip6->nexthdr == IPPROTO_TCP) {
+ struct tcphdr* tcph = (void*)(ip6 + 1);
+
+ // Make sure we can get at the tcp header
+ if (data + l2_header_size + sizeof(*ip6) + sizeof(*tcph) > data_end)
+ PUNT(INVALID_TCP_HEADER);
+
+ // Do not offload TCP packets with any one of the SYN/FIN/RST flags
+ if (tcph->syn || tcph->fin || tcph->rst) PUNT(TCP_CONTROL_PACKET);
+ }
// Protect against forwarding packets sourced from ::1 or fe80::/64 or other weirdness.
__be32 src32 = ip6->saddr.s6_addr32[0];
if (src32 != htonl(0x0064ff9b) && // 64:ff9b:/32 incl. XLAT464 WKP
(src32 & htonl(0xe0000000)) != htonl(0x20000000)) // 2000::/3 Global Unicast
- return TC_ACT_OK;
+ PUNT(NON_GLOBAL_SRC);
- TetherIngressKey k = {
+ // Protect against forwarding packets destined to ::1 or fe80::/64 or other weirdness.
+ __be32 dst32 = ip6->daddr.s6_addr32[0];
+ if (dst32 != htonl(0x0064ff9b) && // 64:ff9b:/32 incl. XLAT464 WKP
+ (dst32 & htonl(0xe0000000)) != htonl(0x20000000)) // 2000::/3 Global Unicast
+ PUNT(NON_GLOBAL_DST);
+
+ // In the upstream direction do not forward traffic within the same /64 subnet.
+ if (!downstream && (src32 == dst32) && (ip6->saddr.s6_addr32[1] == ip6->daddr.s6_addr32[1]))
+ PUNT(LOCAL_SRC_DST);
+
+ TetherDownstream6Key kd = {
.iif = skb->ifindex,
.neigh6 = ip6->daddr,
};
- TetherIngressValue* v = bpf_tether_ingress_map_lookup_elem(&k);
+ TetherUpstream6Key ku = {
+ .iif = skb->ifindex,
+ };
+
+ Tether6Value* v = downstream ? bpf_tether_downstream6_map_lookup_elem(&kd)
+ : bpf_tether_upstream6_map_lookup_elem(&ku);
// If we don't find any offload information then simply let the core stack handle it...
if (!v) return TC_ACT_OK;
- uint32_t stat_and_limit_k = skb->ifindex;
+ uint32_t stat_and_limit_k = downstream ? skb->ifindex : v->oif;
TetherStatsValue* stat_v = bpf_tether_stats_map_lookup_elem(&stat_and_limit_k);
// If we don't have anywhere to put stats, then abort...
- if (!stat_v) return TC_ACT_OK;
+ if (!stat_v) PUNT(NO_STATS_ENTRY);
uint64_t* limit_v = bpf_tether_limit_map_lookup_elem(&stat_and_limit_k);
// If we don't have a limit, then abort...
- if (!limit_v) return TC_ACT_OK;
+ if (!limit_v) PUNT(NO_LIMIT_ENTRY);
// Required IPv6 minimum mtu is 1280, below that not clear what we should do, abort...
- const int pmtu = v->pmtu;
- if (pmtu < IPV6_MIN_MTU) return TC_ACT_OK;
+ if (v->pmtu < IPV6_MIN_MTU) PUNT(BELOW_IPV6_MTU);
// Approximate handling of TCP/IPv6 overhead for incoming LRO/GRO packets: default
// outbound path mtu of 1500 is not necessarily correct, but worst case we simply
@@ -98,9 +196,9 @@
// (This is also blindly assuming 12 bytes of tcp timestamp option in tcp header)
uint64_t packets = 1;
uint64_t bytes = skb->len;
- if (bytes > pmtu) {
+ if (bytes > v->pmtu) {
const int tcp_overhead = sizeof(struct ipv6hdr) + sizeof(struct tcphdr) + 12;
- const int mss = pmtu - tcp_overhead;
+ const int mss = v->pmtu - tcp_overhead;
const uint64_t payload = bytes - tcp_overhead;
packets = (payload + mss - 1) / mss;
bytes = tcp_overhead * packets + payload;
@@ -112,15 +210,15 @@
// a packet we let the core stack deal with things.
// (The core stack needs to handle limits correctly anyway,
// since we don't offload all traffic in both directions)
- if (stat_v->rxBytes + stat_v->txBytes + bytes > *limit_v) return TC_ACT_OK;
+ if (stat_v->rxBytes + stat_v->txBytes + bytes > *limit_v) PUNT(LIMIT_REACHED);
if (!is_ethernet) {
- is_ethernet = true;
- l2_header_size = sizeof(struct ethhdr);
- // Try to inject an ethernet header, and simply return if we fail
- if (bpf_skb_change_head(skb, l2_header_size, /*flags*/ 0)) {
- __sync_fetch_and_add(&stat_v->rxErrors, 1);
- return TC_ACT_OK;
+ // Try to inject an ethernet header, and simply return if we fail.
+ // We do this even if TX interface is RAWIP and thus does not need an ethernet header,
+ // because this is easier and the kernel will strip extraneous ethernet header.
+ if (bpf_skb_change_head(skb, sizeof(struct ethhdr), /*flags*/ 0)) {
+ __sync_fetch_and_add(downstream ? &stat_v->rxErrors : &stat_v->txErrors, 1);
+ PUNT(CHANGE_HEAD_FAILED);
}
// bpf_skb_change_head() invalidates all pointers - reload them
@@ -130,12 +228,16 @@
ip6 = (void*)(eth + 1);
// I do not believe this can ever happen, but keep the verifier happy...
- if (data + l2_header_size + sizeof(*ip6) > data_end) {
- __sync_fetch_and_add(&stat_v->rxErrors, 1);
- return TC_ACT_SHOT;
+ if (data + sizeof(struct ethhdr) + sizeof(*ip6) > data_end) {
+ __sync_fetch_and_add(downstream ? &stat_v->rxErrors : &stat_v->txErrors, 1);
+ DROP(TOO_SHORT);
}
};
+ // At this point we always have an ethernet header - which will get stripped by the
+ // kernel during transmit through a rawip interface. ie. 'eth' pointer is valid.
+ // Additionally note that 'is_ethernet' and 'l2_header_size' are no longer correct.
+
// CHECKSUM_COMPLETE is a 16-bit one's complement sum,
// thus corrections for it need to be done in 16-byte chunks at even offsets.
// IPv6 nexthdr is at offset 6, while hop limit is at offset 7
@@ -147,10 +249,11 @@
// (-ENOTSUPP) if it isn't.
bpf_csum_update(skb, 0xFFFF - ntohs(old_hl) + ntohs(new_hl));
- __sync_fetch_and_add(&stat_v->rxPackets, packets);
- __sync_fetch_and_add(&stat_v->rxBytes, bytes);
+ __sync_fetch_and_add(downstream ? &stat_v->rxPackets : &stat_v->txPackets, packets);
+ __sync_fetch_and_add(downstream ? &stat_v->rxBytes : &stat_v->txBytes, bytes);
// Overwrite any mac header with the new one
+ // For a rawip tx interface it will simply be a bunch of zeroes and later stripped.
*eth = v->macHeader;
// Redirect to forwarded interface.
@@ -162,9 +265,16 @@
return bpf_redirect(v->oif, 0 /* this is effectively BPF_F_EGRESS */);
}
-SEC("schedcls/ingress/tether_ether")
-int sched_cls_ingress_tether_ether(struct __sk_buff* skb) {
- return do_forward(skb, true);
+DEFINE_BPF_PROG("schedcls/tether_downstream6_ether", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_downstream6_ether)
+(struct __sk_buff* skb) {
+ return do_forward6(skb, /* is_ethernet */ true, /* downstream */ true);
+}
+
+DEFINE_BPF_PROG("schedcls/tether_upstream6_ether", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_upstream6_ether)
+(struct __sk_buff* skb) {
+ return do_forward6(skb, /* is_ethernet */ true, /* downstream */ false);
}
// Note: section names must be unique to prevent programs from appending to each other,
@@ -179,29 +289,409 @@
// 5.4 kernel support was only added to Android Common Kernel in R,
// and thus a 5.4 kernel always supports this.
//
-// Hence, this mandatory (must load successfully) implementation for 5.4+ kernels:
-DEFINE_BPF_PROG_KVER("schedcls/ingress/tether_rawip$5_4", AID_ROOT, AID_ROOT,
- sched_cls_ingress_tether_rawip_5_4, KVER(5, 4, 0))
+// Hence, these mandatory (must load successfully) implementations for 5.4+ kernels:
+DEFINE_BPF_PROG_KVER("schedcls/tether_downstream6_rawip$5_4", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_downstream6_rawip_5_4, KVER(5, 4, 0))
(struct __sk_buff* skb) {
- return do_forward(skb, false);
+ return do_forward6(skb, /* is_ethernet */ false, /* downstream */ true);
}
-// and this identical optional (may fail to load) implementation for [4.14..5.4) patched kernels:
-DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/ingress/tether_rawip$4_14", AID_ROOT, AID_ROOT,
- sched_cls_ingress_tether_rawip_4_14, KVER(4, 14, 0),
- KVER(5, 4, 0))
+DEFINE_BPF_PROG_KVER("schedcls/tether_upstream6_rawip$5_4", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_upstream6_rawip_5_4, KVER(5, 4, 0))
(struct __sk_buff* skb) {
- return do_forward(skb, false);
+ return do_forward6(skb, /* is_ethernet */ false, /* downstream */ false);
}
-// and define a no-op stub for [4.9,4.14) and unpatched [4.14,5.4) kernels.
+// and these identical optional (may fail to load) implementations for [4.14..5.4) patched kernels:
+DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream6_rawip$4_14",
+ AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_downstream6_rawip_4_14,
+ KVER(4, 14, 0), KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+ return do_forward6(skb, /* is_ethernet */ false, /* downstream */ true);
+}
+
+DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream6_rawip$4_14",
+ AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_upstream6_rawip_4_14,
+ KVER(4, 14, 0), KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+ return do_forward6(skb, /* is_ethernet */ false, /* downstream */ false);
+}
+
+// and define no-op stubs for [4.9,4.14) and unpatched [4.14,5.4) kernels.
// (if the above real 4.14+ program loaded successfully, then bpfloader will have already pinned
// it at the same location this one would be pinned at and will thus skip loading this stub)
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/ingress/tether_rawip$stub", AID_ROOT, AID_ROOT,
- sched_cls_ingress_tether_rawip_stub, KVER_NONE, KVER(5, 4, 0))
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream6_rawip$stub", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_downstream6_rawip_stub, KVER_NONE, KVER(5, 4, 0))
(struct __sk_buff* skb) {
return TC_ACT_OK;
}
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream6_rawip$stub", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_upstream6_rawip_stub, KVER_NONE, KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+ return TC_ACT_OK;
+}
+
+// ----- IPv4 Support -----
+
+DEFINE_BPF_MAP_GRW(tether_downstream4_map, HASH, Tether4Key, Tether4Value, 64, AID_NETWORK_STACK)
+
+DEFINE_BPF_MAP_GRW(tether_upstream4_map, HASH, Tether4Key, Tether4Value, 64, AID_NETWORK_STACK)
+
+static inline __always_inline int do_forward4(struct __sk_buff* skb, const bool is_ethernet,
+ const bool downstream, const bool updatetime) {
+ const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
+ void* data = (void*)(long)skb->data;
+ const void* data_end = (void*)(long)skb->data_end;
+ struct ethhdr* eth = is_ethernet ? data : NULL; // used iff is_ethernet
+ struct iphdr* ip = is_ethernet ? (void*)(eth + 1) : data;
+
+ // Require ethernet dst mac address to be our unicast address.
+ if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_OK;
+
+ // Must be meta-ethernet IPv4 frame
+ if (skb->protocol != htons(ETH_P_IP)) return TC_ACT_OK;
+
+ // Must have (ethernet and) ipv4 header
+ if (data + l2_header_size + sizeof(*ip) > data_end) return TC_ACT_OK;
+
+ // Ethertype - if present - must be IPv4
+ if (is_ethernet && (eth->h_proto != htons(ETH_P_IP))) return TC_ACT_OK;
+
+ // IP version must be 4
+ if (ip->version != 4) PUNT(INVALID_IP_VERSION);
+
+ // We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header
+ if (ip->ihl != 5) PUNT(HAS_IP_OPTIONS);
+
+ // Calculate the IPv4 one's complement checksum of the IPv4 header.
+ __wsum sum4 = 0;
+ for (int i = 0; i < sizeof(*ip) / sizeof(__u16); ++i) {
+ sum4 += ((__u16*)ip)[i];
+ }
+ // Note that sum4 is guaranteed to be non-zero by virtue of ip4->version == 4
+ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse u32 into range 1 .. 0x1FFFE
+ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse any potential carry into u16
+ // for a correct checksum we should get *a* zero, but sum4 must be positive, ie 0xFFFF
+ if (sum4 != 0xFFFF) PUNT(CHECKSUM);
+
+ // Minimum IPv4 total length is the size of the header
+ if (ntohs(ip->tot_len) < sizeof(*ip)) PUNT(TRUNCATED_IPV4);
+
+ // We are incapable of dealing with IPv4 fragments
+ if (ip->frag_off & ~htons(IP_DF)) PUNT(IS_IP_FRAG);
+
+ // Cannot decrement during forward if already zero or would be zero,
+ // Let the kernel's stack handle these cases and generate appropriate ICMP errors.
+ if (ip->ttl <= 1) PUNT(LOW_TTL);
+
+ // If we cannot update the 'last_used' field due to lack of bpf_ktime_get_boot_ns() helper,
+ // then it is not safe to offload UDP due to the small conntrack timeouts, as such,
+ // in such a situation we can only support TCP. This also has the added nice benefit of
+ // using a separate error counter, and thus making it obvious which version of the program
+ // is loaded.
+ if (!updatetime && ip->protocol != IPPROTO_TCP) PUNT(NON_TCP);
+
+ // We do not support offloading anything besides IPv4 TCP and UDP, due to need for NAT,
+ // but no need to check this if !updatetime due to check immediately above.
+ if (updatetime && (ip->protocol != IPPROTO_TCP) && (ip->protocol != IPPROTO_UDP))
+ PUNT(NON_TCP_UDP);
+
+ // We want to make sure that the compiler will, in the !updatetime case, entirely optimize
+ // out all the non-tcp logic. Also note that at this point is_udp === !is_tcp.
+ const bool is_tcp = !updatetime || (ip->protocol == IPPROTO_TCP);
+
+ struct tcphdr* tcph = is_tcp ? (void*)(ip + 1) : NULL;
+ struct udphdr* udph = is_tcp ? NULL : (void*)(ip + 1);
+
+ if (is_tcp) {
+ // Make sure we can get at the tcp header
+ if (data + l2_header_size + sizeof(*ip) + sizeof(*tcph) > data_end) PUNT(SHORT_TCP_HEADER);
+
+ // If hardware offload is running and programming flows based on conntrack entries, try not
+ // to interfere with it, so do not offload TCP packets with any one of the SYN/FIN/RST flags
+ if (tcph->syn || tcph->fin || tcph->rst) PUNT(TCP_CONTROL_PACKET);
+ } else { // UDP
+ // Make sure we can get at the udp header
+ if (data + l2_header_size + sizeof(*ip) + sizeof(*udph) > data_end) PUNT(SHORT_UDP_HEADER);
+ }
+
+ Tether4Key k = {
+ .iif = skb->ifindex,
+ .l4Proto = ip->protocol,
+ .src4.s_addr = ip->saddr,
+ .dst4.s_addr = ip->daddr,
+ .srcPort = is_tcp ? tcph->source : udph->source,
+ .dstPort = is_tcp ? tcph->dest : udph->dest,
+ };
+ if (is_ethernet) for (int i = 0; i < ETH_ALEN; ++i) k.dstMac[i] = eth->h_dest[i];
+
+ Tether4Value* v = downstream ? bpf_tether_downstream4_map_lookup_elem(&k)
+ : bpf_tether_upstream4_map_lookup_elem(&k);
+
+ // If we don't find any offload information then simply let the core stack handle it...
+ if (!v) return TC_ACT_OK;
+
+ uint32_t stat_and_limit_k = downstream ? skb->ifindex : v->oif;
+
+ TetherStatsValue* stat_v = bpf_tether_stats_map_lookup_elem(&stat_and_limit_k);
+
+ // If we don't have anywhere to put stats, then abort...
+ if (!stat_v) PUNT(NO_STATS_ENTRY);
+
+ uint64_t* limit_v = bpf_tether_limit_map_lookup_elem(&stat_and_limit_k);
+
+ // If we don't have a limit, then abort...
+ if (!limit_v) PUNT(NO_LIMIT_ENTRY);
+
+ // Required IPv4 minimum mtu is 68, below that not clear what we should do, abort...
+ if (v->pmtu < 68) PUNT(BELOW_IPV4_MTU);
+
+ // Approximate handling of TCP/IPv4 overhead for incoming LRO/GRO packets: default
+ // outbound path mtu of 1500 is not necessarily correct, but worst case we simply
+ // undercount, which is still better then not accounting for this overhead at all.
+ // Note: this really shouldn't be device/path mtu at all, but rather should be
+ // derived from this particular connection's mss (ie. from gro segment size).
+ // This would require a much newer kernel with newer ebpf accessors.
+ // (This is also blindly assuming 12 bytes of tcp timestamp option in tcp header)
+ uint64_t packets = 1;
+ uint64_t bytes = skb->len;
+ if (bytes > v->pmtu) {
+ const int tcp_overhead = sizeof(struct iphdr) + sizeof(struct tcphdr) + 12;
+ const int mss = v->pmtu - tcp_overhead;
+ const uint64_t payload = bytes - tcp_overhead;
+ packets = (payload + mss - 1) / mss;
+ bytes = tcp_overhead * packets + payload;
+ }
+
+ // Are we past the limit? If so, then abort...
+ // Note: will not overflow since u64 is 936 years even at 5Gbps.
+ // Do not drop here. Offload is just that, whenever we fail to handle
+ // a packet we let the core stack deal with things.
+ // (The core stack needs to handle limits correctly anyway,
+ // since we don't offload all traffic in both directions)
+ if (stat_v->rxBytes + stat_v->txBytes + bytes > *limit_v) PUNT(LIMIT_REACHED);
+
+ if (!is_tcp) PUNT(NON_TCP); // TEMP HACK: will remove once UDP is supported.
+
+ if (!is_ethernet) {
+ // Try to inject an ethernet header, and simply return if we fail.
+ // We do this even if TX interface is RAWIP and thus does not need an ethernet header,
+ // because this is easier and the kernel will strip extraneous ethernet header.
+ if (bpf_skb_change_head(skb, sizeof(struct ethhdr), /*flags*/ 0)) {
+ __sync_fetch_and_add(downstream ? &stat_v->rxErrors : &stat_v->txErrors, 1);
+ PUNT(CHANGE_HEAD_FAILED);
+ }
+
+ // bpf_skb_change_head() invalidates all pointers - reload them
+ data = (void*)(long)skb->data;
+ data_end = (void*)(long)skb->data_end;
+ eth = data;
+ ip = (void*)(eth + 1);
+ tcph = is_tcp ? (void*)(ip + 1) : NULL;
+ udph = is_tcp ? NULL : (void*)(ip + 1);
+
+ // I do not believe this can ever happen, but keep the verifier happy...
+ if (data + sizeof(struct ethhdr) + sizeof(*ip) + (is_tcp ? sizeof(*tcph) : sizeof(*udph)) > data_end) {
+ __sync_fetch_and_add(downstream ? &stat_v->rxErrors : &stat_v->txErrors, 1);
+ DROP(TOO_SHORT);
+ }
+ };
+
+ // At this point we always have an ethernet header - which will get stripped by the
+ // kernel during transmit through a rawip interface. ie. 'eth' pointer is valid.
+ // Additionally note that 'is_ethernet' and 'l2_header_size' are no longer correct.
+
+ // Overwrite any mac header with the new one
+ // For a rawip tx interface it will simply be a bunch of zeroes and later stripped.
+ *eth = v->macHeader;
+
+ const int sz4 = sizeof(__be32);
+ const __be32 old_daddr = k.dst4.s_addr;
+ const __be32 old_saddr = k.src4.s_addr;
+ const __be32 new_daddr = v->dst46.s6_addr32[3];
+ const __be32 new_saddr = v->src46.s6_addr32[3];
+
+ bpf_l4_csum_replace(skb, ETH_IP4_TCP_OFFSET(check), old_daddr, new_daddr, sz4 | BPF_F_PSEUDO_HDR);
+ bpf_l3_csum_replace(skb, ETH_IP4_OFFSET(check), old_daddr, new_daddr, sz4);
+ bpf_skb_store_bytes(skb, ETH_IP4_OFFSET(daddr), &new_daddr, sz4, 0);
+
+ bpf_l4_csum_replace(skb, ETH_IP4_TCP_OFFSET(check), old_saddr, new_saddr, sz4 | BPF_F_PSEUDO_HDR);
+ bpf_l3_csum_replace(skb, ETH_IP4_OFFSET(check), old_saddr, new_saddr, sz4);
+ bpf_skb_store_bytes(skb, ETH_IP4_OFFSET(saddr), &new_saddr, sz4, 0);
+
+ const int sz2 = sizeof(__be16);
+ bpf_l4_csum_replace(skb, ETH_IP4_TCP_OFFSET(check), k.srcPort, v->srcPort, sz2);
+ bpf_skb_store_bytes(skb, ETH_IP4_TCP_OFFSET(source), &v->srcPort, sz2, 0);
+
+ bpf_l4_csum_replace(skb, ETH_IP4_TCP_OFFSET(check), k.dstPort, v->dstPort, sz2);
+ bpf_skb_store_bytes(skb, ETH_IP4_TCP_OFFSET(dest), &v->dstPort, sz2, 0);
+
+ // TEMP HACK: lack of TTL decrement
+
+ // This requires the bpf_ktime_get_boot_ns() helper which was added in 5.8,
+ // and backported to all Android Common Kernel 4.14+ trees.
+ if (updatetime) v->last_used = bpf_ktime_get_boot_ns();
+
+ __sync_fetch_and_add(downstream ? &stat_v->rxPackets : &stat_v->txPackets, packets);
+ __sync_fetch_and_add(downstream ? &stat_v->rxBytes : &stat_v->txBytes, bytes);
+
+ // Redirect to forwarded interface.
+ //
+ // Note that bpf_redirect() cannot fail unless you pass invalid flags.
+ // The redirect actually happens after the ebpf program has already terminated,
+ // and can fail for example for mtu reasons at that point in time, but there's nothing
+ // we can do about it here.
+ 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);
+}
+
+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))
+(struct __sk_buff* skb) {
+ 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))
+(struct __sk_buff* skb) {
+ return do_forward4(skb, /* is_ethernet */ true, /* downstream */ true, /* updatetime */ true);
+}
+
+DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$opt",
+ AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_downstream4_rawip_opt,
+ KVER(4, 14, 0), KVER(5, 8, 0))
+(struct __sk_buff* skb) {
+ return do_forward4(skb, /* is_ethernet */ false, /* 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);
+}
+
+DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$opt",
+ AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_upstream4_rawip_opt,
+ KVER(4, 14, 0), KVER(5, 8, 0))
+(struct __sk_buff* skb) {
+ return do_forward4(skb, /* is_ethernet */ false, /* 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).
+//
+// [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),
+// this in practice means they'll break only after 5 days. This seems an acceptable trade-off.
+//
+// Additionally kernel/tests change "net-test: add bpf_ktime_get_ns / bpf_ktime_get_boot_ns tests"
+// 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))
+(struct __sk_buff* skb) {
+ return do_forward4(skb, /* is_ethernet */ true, /* 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))
+(struct __sk_buff* skb) {
+ return do_forward4(skb, /* is_ethernet */ false, /* 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 pre-4.14 kernels
+
+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_downstream4_rawip$stub", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_downstream4_rawip_stub, KVER_NONE, KVER(4, 14, 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))
+(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))
+(struct __sk_buff* skb) {
+ return TC_ACT_OK;
+}
+
+// ----- XDP Support -----
+
+#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;
+}
+
+DEFINE_XDP_PROG("xdp/tether_downstream_rawip",
+ xdp_tether_downstream_rawip) {
+ return XDP_PASS;
+}
+
+DEFINE_XDP_PROG("xdp/tether_upstream_ether",
+ xdp_tether_upstream_ether) {
+ return XDP_PASS;
+}
+
+DEFINE_XDP_PROG("xdp/tether_upstream_rawip",
+ xdp_tether_upstream_rawip) {
+ return XDP_PASS;
+}
+
LICENSE("Apache 2.0");
CRITICAL("netd");
diff --git a/Tethering/bpf_progs/test.c b/Tethering/bpf_progs/test.c
index b5be33f..c4a8271 100644
--- a/Tethering/bpf_progs/test.c
+++ b/Tethering/bpf_progs/test.c
@@ -14,12 +14,34 @@
* limitations under the License.
*/
+#include <linux/if_ether.h>
+#include <linux/in.h>
+#include <linux/ip.h>
+
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
#include "netdbpf/bpf_shared.h"
// Used only by TetheringPrivilegedTests, not by production code.
-DEFINE_BPF_MAP_GRW(tether_ingress_map, HASH, TetherIngressKey, TetherIngressValue, 16,
+DEFINE_BPF_MAP_GRW(tether_downstream6_map, HASH, TetherDownstream6Key, Tether6Value, 16,
AID_NETWORK_STACK)
+DEFINE_BPF_PROG_KVER("xdp/drop_ipv4_udp_ether", AID_ROOT, AID_NETWORK_STACK,
+ xdp_test, KVER(5, 9, 0))
+(struct xdp_md *ctx) {
+ void *data = (void *)(long)ctx->data;
+ void *data_end = (void *)(long)ctx->data_end;
+
+ struct ethhdr *eth = data;
+ int hsize = sizeof(*eth);
+
+ struct iphdr *ip = data + hsize;
+ hsize += sizeof(struct iphdr);
+
+ if (data + hsize > data_end) return XDP_PASS;
+ if (eth->h_proto != htons(ETH_P_IP)) return XDP_PASS;
+ if (ip->protocol == IPPROTO_UDP) return XDP_DROP;
+ return XDP_PASS;
+}
+
LICENSE("Apache 2.0");
diff --git a/Tethering/jni/com_android_networkstack_tethering_BpfCoordinator.cpp b/Tethering/jni/com_android_networkstack_tethering_BpfCoordinator.cpp
new file mode 100644
index 0000000..27357f8
--- /dev/null
+++ b/Tethering/jni/com_android_networkstack_tethering_BpfCoordinator.cpp
@@ -0,0 +1,47 @@
+/*
+ * 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 <jni.h>
+#include <nativehelper/JNIHelp.h>
+
+#include "bpf_tethering.h"
+
+namespace android {
+
+static jobjectArray getBpfCounterNames(JNIEnv *env) {
+ size_t size = BPF_TETHER_ERR__MAX;
+ jobjectArray ret = env->NewObjectArray(size, env->FindClass("java/lang/String"), nullptr);
+ for (int i = 0; i < size; i++) {
+ env->SetObjectArrayElement(ret, i, env->NewStringUTF(bpf_tether_errors[i]));
+ }
+ return ret;
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ { "getBpfCounterNames", "()[Ljava/lang/String;", (void*) getBpfCounterNames },
+};
+
+int register_com_android_networkstack_tethering_BpfCoordinator(JNIEnv* env) {
+ return jniRegisterNativeMethods(env,
+ "com/android/networkstack/tethering/BpfCoordinator",
+ gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/Tethering/jni/onload.cpp b/Tethering/jni/onload.cpp
index 3766de9..e31da60 100644
--- a/Tethering/jni/onload.cpp
+++ b/Tethering/jni/onload.cpp
@@ -24,6 +24,7 @@
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);
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
JNIEnv *env;
@@ -36,6 +37,8 @@
if (register_com_android_networkstack_tethering_BpfMap(env) < 0) return JNI_ERR;
+ if (register_com_android_networkstack_tethering_BpfCoordinator(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 52d59fc..194737a 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -67,13 +67,12 @@
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.networkstack.tethering.BpfCoordinator;
+import com.android.networkstack.tethering.BpfCoordinator.ClientInfo;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
-import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
-import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -186,16 +185,6 @@
return InterfaceParams.getByName(ifName);
}
- /** Get |ifName|'s interface index. */
- public int getIfindex(String ifName) {
- try {
- return NetworkInterface.getByName(ifName).getIndex();
- } catch (IOException | NullPointerException e) {
- Log.e(TAG, "Can't determine interface index for interface " + ifName);
- return 0;
- }
- }
-
/** Create a DhcpServer instance to be used by IpServer. */
public abstract void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
DhcpServerCallbacks cb);
@@ -941,11 +930,38 @@
}
}
+ // TODO: consider moving into BpfCoordinator.
+ private void updateClientInfoIpv4(NeighborEvent e) {
+ // TODO: Perhaps remove this protection check.
+ // See the related comment in #addIpv6ForwardingRule.
+ if (!mUsingBpfOffload) return;
+
+ if (e == null) return;
+ if (!(e.ip instanceof Inet4Address) || e.ip.isMulticastAddress()
+ || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) {
+ return;
+ }
+
+ // When deleting clients, IpServer still need to pass a non-null MAC, even though it's
+ // ignored. Do this here instead of in the ClientInfo constructor to ensure that
+ // IpServer never add clients with a null MAC, only delete them.
+ final MacAddress clientMac = e.isValid() ? e.macAddr : NULL_MAC_ADDRESS;
+ final ClientInfo clientInfo = new ClientInfo(mInterfaceParams.index,
+ mInterfaceParams.macAddr, (Inet4Address) e.ip, clientMac);
+ if (e.isValid()) {
+ mBpfCoordinator.tetherOffloadClientAdd(this, clientInfo);
+ } else {
+ // TODO: Delete all related offload rules which are using this client.
+ mBpfCoordinator.tetherOffloadClientRemove(this, clientInfo);
+ }
+ }
+
private void handleNeighborEvent(NeighborEvent e) {
if (mInterfaceParams != null
&& mInterfaceParams.index == e.ifindex
&& mInterfaceParams.hasMacAddress) {
updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, mLastIPv6UpstreamIfindex, e);
+ updateClientInfoIpv4(e);
}
}
@@ -1111,9 +1127,19 @@
}
}
+ private void startConntrackMonitoring() {
+ mBpfCoordinator.startMonitoring(this);
+ }
+
+ private void stopConntrackMonitoring() {
+ mBpfCoordinator.stopMonitoring(this);
+ }
+
class BaseServingState extends State {
@Override
public void enter() {
+ startConntrackMonitoring();
+
if (!startIPv4()) {
mLastError = TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
return;
@@ -1149,6 +1175,7 @@
}
stopIPv4();
+ stopConntrackMonitoring();
resetLinkProperties();
}
diff --git a/Tethering/src/android/net/util/TetheringUtils.java b/Tethering/src/android/net/util/TetheringUtils.java
index 706d78c..9e7cc2f 100644
--- a/Tethering/src/android/net/util/TetheringUtils.java
+++ b/Tethering/src/android/net/util/TetheringUtils.java
@@ -36,6 +36,10 @@
* {@hide}
*/
public class TetheringUtils {
+ static {
+ System.loadLibrary("tetherutilsjni");
+ }
+
public static final byte[] ALL_NODES = new byte[] {
(byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
};
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 64ac37c..985328f 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -23,19 +23,26 @@
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
import static android.net.NetworkStats.UID_TETHERING;
+import static android.net.ip.ConntrackMonitor.ConntrackEvent;
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.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
import android.app.usage.NetworkStatsManager;
import android.net.INetd;
+import android.net.LinkProperties;
import android.net.MacAddress;
import android.net.NetworkStats;
import android.net.NetworkStats.Entry;
import android.net.TetherOffloadRuleParcel;
+import android.net.ip.ConntrackMonitor;
+import android.net.ip.ConntrackMonitor.ConntrackEventConsumer;
import android.net.ip.IpServer;
+import android.net.netlink.NetlinkConstants;
import android.net.netstats.provider.NetworkStatsProvider;
+import android.net.util.InterfaceParams;
import android.net.util.SharedLog;
import android.net.util.TetheringUtils.ForwardedStats;
import android.os.ConditionVariable;
@@ -52,14 +59,21 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.NetworkStackConstants;
+import com.android.net.module.util.Struct;
import com.android.networkstack.tethering.apishim.common.BpfCoordinatorShim;
+import java.net.Inet4Address;
import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
/**
* This coordinator is responsible for providing BPF offload relevant functionality.
@@ -71,14 +85,39 @@
* @hide
*/
public class BpfCoordinator {
+ // Ensure the JNI code is loaded. In production this will already have been loaded by
+ // TetherService, but for tests it needs to be either loaded here or loaded by every test.
+ // TODO: is there a better way?
+ static {
+ 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 String TETHER_INGRESS_FS_PATH =
- "/sys/fs/bpf/map_offload_tether_ingress_map";
- private static final String TETHER_STATS_MAP_PATH =
- "/sys/fs/bpf/map_offload_tether_stats_map";
- private static final String TETHER_LIMIT_MAP_PATH =
- "/sys/fs/bpf/map_offload_tether_limit_map";
+ private static final MacAddress NULL_MAC_ADDRESS = MacAddress.fromString(
+ "00:00:00:00:00:00");
+ private static final String TETHER_DOWNSTREAM4_MAP_PATH = makeMapPath(DOWNSTREAM, 4);
+ private static final String TETHER_UPSTREAM4_MAP_PATH = makeMapPath(UPSTREAM, 4);
+ private static final String TETHER_DOWNSTREAM6_FS_PATH = makeMapPath(DOWNSTREAM, 6);
+ private static final String TETHER_UPSTREAM6_FS_PATH = makeMapPath(UPSTREAM, 6);
+ private static final String TETHER_STATS_MAP_PATH = makeMapPath("stats");
+ private static final String TETHER_LIMIT_MAP_PATH = makeMapPath("limit");
+ private static final String TETHER_ERROR_MAP_PATH = makeMapPath("error");
+
+ /** The names of all the BPF counters defined in bpf_tethering.h. */
+ public static final String[] sBpfCounterNames = getBpfCounterNames();
+
+ private static String makeMapPath(String which) {
+ return "/sys/fs/bpf/tethering/map_offload_tether_" + which + "_map";
+ }
+
+ private static String makeMapPath(boolean downstream, int ipVersion) {
+ return makeMapPath((downstream ? "downstream" : "upstream") + ipVersion);
+ }
+
@VisibleForTesting
enum StatsType {
@@ -94,6 +133,8 @@
private final SharedLog mLog;
@NonNull
private final Dependencies mDeps;
+ @NonNull
+ private final ConntrackMonitor mConntrackMonitor;
@Nullable
private final BpfTetherStatsProvider mStatsProvider;
@NonNull
@@ -156,6 +197,27 @@
private final HashMap<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>>
mIpv6ForwardingRules = new LinkedHashMap<>();
+ // Map of downstream client maps. Each of these maps represents the IPv4 clients for a given
+ // downstream. Needed to build IPv4 forwarding rules when conntrack events are received.
+ // Each map:
+ // - Is owned by the IpServer that is responsible for that downstream.
+ // - Must only be modified by that IpServer.
+ // - Is created when the IpServer adds its first client, and deleted when the IpServer deletes
+ // its last client.
+ // Note that relying on the client address for finding downstream is okay for now because the
+ // client address is unique. See PrivateAddressCoordinator#requestDownstreamAddress.
+ // TODO: Refactor if any possible that the client address is not unique.
+ private final HashMap<IpServer, HashMap<Inet4Address, ClientInfo>>
+ mTetherClients = new HashMap<>();
+
+ // Set for which downstream is monitoring the conntrack netlink message.
+ private final Set<IpServer> mMonitoringIpServers = new HashSet<>();
+
+ // Map of upstream interface IPv4 address to interface index.
+ // TODO: consider making the key to be unique because the upstream address is not unique. It
+ // is okay for now because there have only one upstream generally.
+ private final HashMap<Inet4Address, Integer> mIpv4UpstreamIndices = new HashMap<>();
+
// Runnable that used by scheduling next polling of stats.
private final Runnable mScheduledPollingTask = () -> {
updateForwardedStats();
@@ -179,6 +241,11 @@
/** Get tethering configuration. */
@Nullable public abstract TetheringConfiguration getTetherConfig();
+ /** Get conntrack monitor. */
+ @NonNull public ConntrackMonitor getConntrackMonitor(ConntrackEventConsumer consumer) {
+ return new ConntrackMonitor(getHandler(), getSharedLog(), consumer);
+ }
+
/**
* Check OS Build at least S.
*
@@ -190,13 +257,46 @@
return SdkLevel.isAtLeastS();
}
- /** Get ingress BPF map. */
- @Nullable public BpfMap<TetherIngressKey, TetherIngressValue> getBpfIngressMap() {
+ /** Get downstream4 BPF map. */
+ @Nullable public BpfMap<Tether4Key, Tether4Value> getBpfDownstream4Map() {
try {
- return new BpfMap<>(TETHER_INGRESS_FS_PATH,
- BpfMap.BPF_F_RDWR, TetherIngressKey.class, TetherIngressValue.class);
+ return new BpfMap<>(TETHER_DOWNSTREAM4_MAP_PATH,
+ BpfMap.BPF_F_RDWR, Tether4Key.class, Tether4Value.class);
} catch (ErrnoException e) {
- Log.e(TAG, "Cannot create ingress map: " + e);
+ Log.e(TAG, "Cannot create downstream4 map: " + e);
+ return null;
+ }
+ }
+
+ /** Get upstream4 BPF map. */
+ @Nullable public BpfMap<Tether4Key, Tether4Value> getBpfUpstream4Map() {
+ try {
+ return new BpfMap<>(TETHER_UPSTREAM4_MAP_PATH,
+ BpfMap.BPF_F_RDWR, Tether4Key.class, Tether4Value.class);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot create upstream4 map: " + e);
+ return null;
+ }
+ }
+
+ /** Get downstream6 BPF map. */
+ @Nullable public BpfMap<TetherDownstream6Key, Tether6Value> getBpfDownstream6Map() {
+ try {
+ return new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH,
+ BpfMap.BPF_F_RDWR, TetherDownstream6Key.class, Tether6Value.class);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot create downstream6 map: " + e);
+ return null;
+ }
+ }
+
+ /** Get upstream6 BPF map. */
+ @Nullable public BpfMap<TetherUpstream6Key, Tether6Value> getBpfUpstream6Map() {
+ try {
+ return new BpfMap<>(TETHER_UPSTREAM6_FS_PATH, BpfMap.BPF_F_RDWR,
+ TetherUpstream6Key.class, Tether6Value.class);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot create upstream6 map: " + e);
return null;
}
}
@@ -231,6 +331,7 @@
mNetd = mDeps.getNetd();
mLog = mDeps.getSharedLog().forSubComponent(TAG);
mIsBpfEnabled = isBpfEnabled();
+ mConntrackMonitor = mDeps.getConntrackMonitor(new BpfConntrackEventConsumer());
BpfTetherStatsProvider provider = new BpfTetherStatsProvider();
try {
mDeps.getNetworkStatsManager().registerNetworkStatsProvider(
@@ -295,6 +396,58 @@
}
/**
+ * Start conntrack message monitoring.
+ * Note that this can be only called on handler thread.
+ *
+ * TODO: figure out a better logging for non-interesting conntrack message.
+ * For example, the following logging is an IPCTNL_MSG_CT_GET message but looks scary.
+ * +---------------------------------------------------------------------------+
+ * | ERROR unparsable netlink msg: 1400000001010103000000000000000002000000 |
+ * +------------------+--------------------------------------------------------+
+ * | | struct nlmsghdr |
+ * | 14000000 | length = 20 |
+ * | 0101 | type = NFNL_SUBSYS_CTNETLINK << 8 | IPCTNL_MSG_CT_GET |
+ * | 0103 | flags |
+ * | 00000000 | seqno = 0 |
+ * | 00000000 | pid = 0 |
+ * | | struct nfgenmsg |
+ * | 02 | nfgen_family = AF_INET |
+ * | 00 | version = NFNETLINK_V0 |
+ * | 0000 | res_id |
+ * +------------------+--------------------------------------------------------+
+ * See NetlinkMonitor#handlePacket, NetlinkMessage#parseNfMessage.
+ */
+ public void startMonitoring(@NonNull final IpServer ipServer) {
+ if (!isUsingBpf()) return;
+
+ if (mMonitoringIpServers.contains(ipServer)) {
+ Log.wtf(TAG, "The same downstream " + ipServer.interfaceName()
+ + " should not start monitoring twice.");
+ return;
+ }
+
+ if (mMonitoringIpServers.isEmpty()) {
+ mConntrackMonitor.start();
+ mLog.i("Monitoring started");
+ }
+
+ mMonitoringIpServers.add(ipServer);
+ }
+
+ /**
+ * Stop conntrack event monitoring.
+ * Note that this can be only called on handler thread.
+ */
+ public void stopMonitoring(@NonNull final IpServer ipServer) {
+ mMonitoringIpServers.remove(ipServer);
+
+ if (!mMonitoringIpServers.isEmpty()) return;
+
+ mConntrackMonitor.stop();
+ mLog.i("Monitoring stopped");
+ }
+
+ /**
* Add forwarding rule. After adding the first rule on a given upstream, must add the data
* limit on the given upstream.
* Note that this can be only called on handler thread.
@@ -312,7 +465,7 @@
}
LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(ipServer);
- // Setup the data limit on the given upstream if the first rule is added.
+ // When the first rule is added to an upstream, setup upstream forwarding and data limit.
final int upstreamIfindex = rule.upstreamIfindex;
if (!isAnyRuleOnUpstream(upstreamIfindex)) {
// If failed to set a data limit, probably should not use this upstream, because
@@ -323,6 +476,19 @@
final String iface = mInterfaceNames.get(upstreamIfindex);
mLog.e("Setting data limit for " + iface + " failed.");
}
+
+ }
+
+ if (!isAnyRuleFromDownstreamToUpstream(rule.downstreamIfindex, rule.upstreamIfindex)) {
+ final int downstream = rule.downstreamIfindex;
+ final int upstream = rule.upstreamIfindex;
+ // TODO: support upstream forwarding on non-point-to-point interfaces.
+ // TODO: get the MTU from LinkProperties and update the rules when it changes.
+ if (!mBpfCoordinatorShim.startUpstreamIpv6Forwarding(downstream, upstream,
+ NULL_MAC_ADDRESS, NULL_MAC_ADDRESS, NetworkStackConstants.ETHER_MTU)) {
+ mLog.e("Failed to enable upstream IPv6 forwarding from "
+ + mInterfaceNames.get(downstream) + " to " + mInterfaceNames.get(upstream));
+ }
}
// Must update the adding rule after calling #isAnyRuleOnUpstream because it needs to
@@ -355,6 +521,16 @@
mIpv6ForwardingRules.remove(ipServer);
}
+ // If no more rules between this upstream and downstream, stop upstream forwarding.
+ if (!isAnyRuleFromDownstreamToUpstream(rule.downstreamIfindex, rule.upstreamIfindex)) {
+ final int downstream = rule.downstreamIfindex;
+ final int upstream = rule.upstreamIfindex;
+ if (!mBpfCoordinatorShim.stopUpstreamIpv6Forwarding(downstream, upstream)) {
+ mLog.e("Failed to disable upstream IPv6 forwarding from "
+ + mInterfaceNames.get(downstream) + " to " + mInterfaceNames.get(upstream));
+ }
+ }
+
// Do cleanup functionality if there is no more rule on the given upstream.
final int upstreamIfindex = rule.upstreamIfindex;
if (!isAnyRuleOnUpstream(upstreamIfindex)) {
@@ -403,12 +579,22 @@
if (rules == null) return;
// Need to build a rule list because the rule map may be changed in the iteration.
- for (final Ipv6ForwardingRule rule : new ArrayList<Ipv6ForwardingRule>(rules.values())) {
+ // First remove all the old rules, then add all the new rules. This is because the upstream
+ // forwarding code in tetherOffloadRuleAdd cannot support rules on two upstreams at the
+ // same time. Deleting the rules first ensures that upstream forwarding is disabled on the
+ // old upstream when the last rule is removed from it, and re-enabled on the new upstream
+ // when the first rule is added to it.
+ // TODO: Once the IPv6 client processing code has moved from IpServer to BpfCoordinator, do
+ // something smarter.
+ final ArrayList<Ipv6ForwardingRule> rulesCopy = new ArrayList<>(rules.values());
+ for (final Ipv6ForwardingRule rule : rulesCopy) {
// Remove the old rule before adding the new one because the map uses the same key for
// both rules. Reversing the processing order causes that the new rule is removed as
// unexpected.
// TODO: Add new rule first to reduce the latency which has no rule.
tetherOffloadRuleRemove(ipServer, rule);
+ }
+ for (final Ipv6ForwardingRule rule : rulesCopy) {
tetherOffloadRuleAdd(ipServer, rule.onNewUpstream(newUpstreamIfindex));
}
}
@@ -437,6 +623,80 @@
}
/**
+ * Add downstream client.
+ */
+ public void tetherOffloadClientAdd(@NonNull final IpServer ipServer,
+ @NonNull final ClientInfo client) {
+ if (!isUsingBpf()) return;
+
+ if (!mTetherClients.containsKey(ipServer)) {
+ mTetherClients.put(ipServer, new HashMap<Inet4Address, ClientInfo>());
+ }
+
+ HashMap<Inet4Address, ClientInfo> clients = mTetherClients.get(ipServer);
+ clients.put(client.clientAddress, client);
+ }
+
+ /**
+ * Remove downstream client.
+ */
+ public void tetherOffloadClientRemove(@NonNull final IpServer ipServer,
+ @NonNull final ClientInfo client) {
+ if (!isUsingBpf()) return;
+
+ HashMap<Inet4Address, ClientInfo> clients = mTetherClients.get(ipServer);
+ if (clients == null) return;
+
+ // If no rule is removed, return early. Avoid unnecessary work on a non-existent rule
+ // which may have never been added or removed already.
+ if (clients.remove(client.clientAddress) == null) return;
+
+ // Remove the downstream entry if it has no more rule.
+ if (clients.isEmpty()) {
+ mTetherClients.remove(ipServer);
+ }
+ }
+
+ /**
+ * Call when UpstreamNetworkState may be changed.
+ * If upstream has ipv4 for tethering, update this new UpstreamNetworkState to map. The
+ * upstream interface index and its address mapping is prepared for building IPv4
+ * offload rule.
+ *
+ * TODO: Delete the unused upstream interface mapping.
+ * TODO: Support ether ip upstream interface.
+ */
+ public void addUpstreamIfindexToMap(LinkProperties lp) {
+ if (!mPollingStarted) return;
+
+ // This will not work on a network that is using 464xlat because hasIpv4Address will not be
+ // true.
+ // TODO: need to consider 464xlat.
+ if (lp == null || !lp.hasIpv4Address()) return;
+
+ // Support raw ip upstream interface only.
+ final InterfaceParams params = InterfaceParams.getByName(lp.getInterfaceName());
+ if (params == null || params.hasMacAddress) return;
+
+ Collection<InetAddress> addresses = lp.getAddresses();
+ for (InetAddress addr: addresses) {
+ if (addr instanceof Inet4Address) {
+ Inet4Address i4addr = (Inet4Address) addr;
+ if (!i4addr.isAnyLocalAddress() && !i4addr.isLinkLocalAddress()
+ && !i4addr.isLoopbackAddress() && !i4addr.isMulticastAddress()) {
+ mIpv4UpstreamIndices.put(i4addr, params.index);
+ }
+ }
+ }
+ }
+
+
+ // TODO: make mInterfaceNames accessible to the shim and move this code to there.
+ private String getIfName(long ifindex) {
+ return mInterfaceNames.get((int) ifindex, Long.toString(ifindex));
+ }
+
+ /**
* Dump information.
* Block the function until all the data are dumped on the handler thread or timed-out. The
* reason is that dumpsys invokes this function on the thread of caller and the data may only
@@ -464,11 +724,15 @@
pw.println("Forwarding rules:");
pw.increaseIndent();
- if (mIpv6ForwardingRules.size() == 0) {
- pw.println("<empty>");
- } else {
- dumpIpv6ForwardingRules(pw);
- }
+ dumpIpv6UpstreamRules(pw);
+ dumpIpv6ForwardingRules(pw);
+ dumpIpv4ForwardingRules(pw);
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("Forwarding counters:");
+ pw.increaseIndent();
+ dumpCounters(pw);
pw.decreaseIndent();
dumpDone.open();
@@ -488,6 +752,11 @@
}
private void dumpIpv6ForwardingRules(@NonNull IndentingPrintWriter pw) {
+ if (mIpv6ForwardingRules.size() == 0) {
+ pw.println("No IPv6 rules");
+ return;
+ }
+
for (Map.Entry<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>> entry :
mIpv6ForwardingRules.entrySet()) {
IpServer ipServer = entry.getKey();
@@ -508,11 +777,97 @@
}
}
+ private String ipv6UpstreamRuletoString(TetherUpstream6Key key, Tether6Value value) {
+ return String.format("%d(%s) -> %d(%s) %04x %s %s",
+ key.iif, getIfName(key.iif), value.oif, getIfName(value.oif),
+ value.ethProto, value.ethSrcMac, value.ethDstMac);
+ }
+
+ private void dumpIpv6UpstreamRules(IndentingPrintWriter pw) {
+ try (BpfMap<TetherUpstream6Key, Tether6Value> map = mDeps.getBpfUpstream6Map()) {
+ if (map == null) {
+ pw.println("No IPv6 upstream");
+ return;
+ }
+ if (map.isEmpty()) {
+ pw.println("No IPv6 upstream rules");
+ return;
+ }
+ map.forEach((k, v) -> pw.println(ipv6UpstreamRuletoString(k, v)));
+ } catch (ErrnoException e) {
+ pw.println("Error dumping IPv4 map: " + e);
+ }
+ }
+
+ private String ipv4RuleToString(Tether4Key key, Tether4Value value) {
+ final String private4, public4, dst4;
+ try {
+ private4 = InetAddress.getByAddress(key.src4).getHostAddress();
+ dst4 = InetAddress.getByAddress(key.dst4).getHostAddress();
+ public4 = InetAddress.getByAddress(value.src46).getHostAddress();
+ } 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);
+ }
+
+ private void dumpIpv4ForwardingRules(IndentingPrintWriter pw) {
+ try (BpfMap<Tether4Key, Tether4Value> map = mDeps.getBpfUpstream4Map()) {
+ if (map == null) {
+ pw.println("No IPv4 support");
+ return;
+ }
+ if (map.isEmpty()) {
+ pw.println("No IPv4 rules");
+ return;
+ }
+ pw.println("[IPv4]: iif(iface) oif(iface) src nat dst");
+ pw.increaseIndent();
+ map.forEach((k, v) -> pw.println(ipv4RuleToString(k, v)));
+ } catch (ErrnoException e) {
+ pw.println("Error dumping IPv4 map: " + e);
+ }
+ pw.decreaseIndent();
+ }
+
+ /**
+ * Simple struct that only contains a u32. Must be public because Struct needs access to it.
+ * TODO: make this a public inner class of Struct so anyone can use it as, e.g., Struct.U32?
+ */
+ public static class U32Struct extends Struct {
+ @Struct.Field(order = 0, type = Struct.Type.U32)
+ public long val;
+ }
+
+ private void dumpCounters(@NonNull IndentingPrintWriter pw) {
+ try (BpfMap<U32Struct, U32Struct> map = new BpfMap<>(TETHER_ERROR_MAP_PATH,
+ BpfMap.BPF_F_RDONLY, U32Struct.class, U32Struct.class)) {
+
+ map.forEach((k, v) -> {
+ String counterName;
+ try {
+ counterName = sBpfCounterNames[(int) k.val];
+ } catch (IndexOutOfBoundsException e) {
+ // Should never happen because this code gets the counter name from the same
+ // include file as the BPF program that increments the counter.
+ Log.wtf(TAG, "Unknown tethering counter type " + k.val);
+ counterName = Long.toString(k.val);
+ }
+ if (v.val > 0) pw.println(String.format("%s: %d", counterName, v.val));
+ });
+ } catch (ErrnoException e) {
+ pw.println("Error dumping counter map: " + e);
+ }
+ }
+
/** IPv6 forwarding rule class. */
public static class Ipv6ForwardingRule {
public final int upstreamIfindex;
public final int downstreamIfindex;
+ // TODO: store a ClientInfo object instead of storing address, srcMac, and dstMac directly.
@NonNull
public final Inet6Address address;
@NonNull
@@ -554,19 +909,19 @@
}
/**
- * Return a TetherIngressKey object built from the rule.
+ * Return a TetherDownstream6Key object built from the rule.
*/
@NonNull
- public TetherIngressKey makeTetherIngressKey() {
- return new TetherIngressKey(upstreamIfindex, address.getAddress());
+ public TetherDownstream6Key makeTetherDownstream6Key() {
+ return new TetherDownstream6Key(upstreamIfindex, address.getAddress());
}
/**
- * Return a TetherIngressValue object built from the rule.
+ * Return a Tether6Value object built from the rule.
*/
@NonNull
- public TetherIngressValue makeTetherIngressValue() {
- return new TetherIngressValue(downstreamIfindex, dstMac, srcMac, ETH_P_IPV6,
+ public Tether6Value makeTether6Value() {
+ return new Tether6Value(downstreamIfindex, dstMac, srcMac, ETH_P_IPV6,
NetworkStackConstants.ETHER_MTU);
}
@@ -589,6 +944,48 @@
}
}
+ /** Tethering client information class. */
+ public static class ClientInfo {
+ public final int downstreamIfindex;
+
+ @NonNull
+ public final MacAddress downstreamMac;
+ @NonNull
+ public final Inet4Address clientAddress;
+ @NonNull
+ public final MacAddress clientMac;
+
+ public ClientInfo(int downstreamIfindex,
+ @NonNull MacAddress downstreamMac, @NonNull Inet4Address clientAddress,
+ @NonNull MacAddress clientMac) {
+ this.downstreamIfindex = downstreamIfindex;
+ this.downstreamMac = downstreamMac;
+ this.clientAddress = clientAddress;
+ this.clientMac = clientMac;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ClientInfo)) return false;
+ ClientInfo that = (ClientInfo) o;
+ return this.downstreamIfindex == that.downstreamIfindex
+ && Objects.equals(this.downstreamMac, that.downstreamMac)
+ && Objects.equals(this.clientAddress, that.clientAddress)
+ && Objects.equals(this.clientMac, that.clientMac);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(downstreamIfindex, downstreamMac, clientAddress, clientMac);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("downstream: %d (%s), client: %s (%s)",
+ downstreamIfindex, downstreamMac, clientAddress, clientMac);
+ }
+ }
+
/**
* A BPF tethering stats provider to provide network statistics to the system.
* Note that this class' data may only be accessed on the handler thread.
@@ -655,6 +1052,99 @@
}
}
+ @Nullable
+ private ClientInfo getClientInfo(@NonNull Inet4Address clientAddress) {
+ for (HashMap<Inet4Address, ClientInfo> clients : mTetherClients.values()) {
+ for (ClientInfo client : clients.values()) {
+ if (clientAddress.equals(client.clientAddress)) {
+ return client;
+ }
+ }
+ }
+ return null;
+ }
+
+ // Support raw ip only.
+ // TODO: add ether ip support.
+ private class BpfConntrackEventConsumer implements ConntrackEventConsumer {
+ @NonNull
+ private Tether4Key makeTetherUpstream4Key(
+ @NonNull ConntrackEvent e, @NonNull ClientInfo c) {
+ return new Tether4Key(c.downstreamIfindex, c.downstreamMac,
+ e.tupleOrig.protoNum, e.tupleOrig.srcIp.getAddress(),
+ e.tupleOrig.dstIp.getAddress(), e.tupleOrig.srcPort, e.tupleOrig.dstPort);
+ }
+
+ @NonNull
+ private Tether4Key makeTetherDownstream4Key(
+ @NonNull ConntrackEvent e, @NonNull ClientInfo c, int upstreamIndex) {
+ return new Tether4Key(upstreamIndex, NULL_MAC_ADDRESS /* dstMac (rawip) */,
+ e.tupleReply.protoNum, e.tupleReply.srcIp.getAddress(),
+ e.tupleReply.dstIp.getAddress(), e.tupleReply.srcPort, e.tupleReply.dstPort);
+ }
+
+ @NonNull
+ private Tether4Value makeTetherUpstream4Value(@NonNull ConntrackEvent e,
+ int upstreamIndex) {
+ return new Tether4Value(upstreamIndex,
+ NULL_MAC_ADDRESS /* ethDstMac (rawip) */,
+ NULL_MAC_ADDRESS /* ethSrcMac (rawip) */, ETH_P_IP,
+ NetworkStackConstants.ETHER_MTU, toIpv4MappedAddressBytes(e.tupleReply.dstIp),
+ toIpv4MappedAddressBytes(e.tupleReply.srcIp), e.tupleReply.dstPort,
+ e.tupleReply.srcPort, 0 /* lastUsed, filled by bpf prog only */);
+ }
+
+ @NonNull
+ private Tether4Value makeTetherDownstream4Value(@NonNull ConntrackEvent e,
+ @NonNull ClientInfo c, int upstreamIndex) {
+ return new Tether4Value(c.downstreamIfindex,
+ c.clientMac, c.downstreamMac, ETH_P_IP, NetworkStackConstants.ETHER_MTU,
+ toIpv4MappedAddressBytes(e.tupleOrig.dstIp),
+ toIpv4MappedAddressBytes(e.tupleOrig.srcIp),
+ e.tupleOrig.dstPort, e.tupleOrig.srcPort,
+ 0 /* lastUsed, filled by bpf prog only */);
+ }
+
+ @NonNull
+ private byte[] toIpv4MappedAddressBytes(Inet4Address ia4) {
+ final byte[] addr4 = ia4.getAddress();
+ final byte[] addr6 = new byte[16];
+ addr6[10] = (byte) 0xff;
+ addr6[11] = (byte) 0xff;
+ addr6[12] = addr4[0];
+ addr6[13] = addr4[1];
+ addr6[14] = addr4[2];
+ addr6[15] = addr4[3];
+ return addr6;
+ }
+
+ public void accept(ConntrackEvent e) {
+ final ClientInfo tetherClient = getClientInfo(e.tupleOrig.srcIp);
+ if (tetherClient == null) return;
+
+ final Integer upstreamIndex = mIpv4UpstreamIndices.get(e.tupleReply.dstIp);
+ if (upstreamIndex == null) return;
+
+ final Tether4Key upstream4Key = makeTetherUpstream4Key(e, tetherClient);
+ final Tether4Key downstream4Key = makeTetherDownstream4Key(e, tetherClient,
+ upstreamIndex);
+
+ if (e.msgType == (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8
+ | NetlinkConstants.IPCTNL_MSG_CT_DELETE)) {
+ mBpfCoordinatorShim.tetherOffloadRuleRemove(false, upstream4Key);
+ mBpfCoordinatorShim.tetherOffloadRuleRemove(true, downstream4Key);
+ return;
+ }
+
+ final Tether4Value upstream4Value = makeTetherUpstream4Value(e, upstreamIndex);
+ final Tether4Value downstream4Value = makeTetherDownstream4Value(e, tetherClient,
+ upstreamIndex);
+
+ mBpfCoordinatorShim.tetherOffloadRuleAdd(false, upstream4Key, upstream4Value);
+ mBpfCoordinatorShim.tetherOffloadRuleAdd(true, downstream4Key, downstream4Value);
+ }
+ }
+
private boolean isBpfEnabled() {
final TetheringConfiguration config = mDeps.getTetherConfig();
return (config != null) ? config.isBpfOffloadEnabled() : true /* default value */;
@@ -723,6 +1213,19 @@
return false;
}
+ private boolean isAnyRuleFromDownstreamToUpstream(int downstreamIfindex, int upstreamIfindex) {
+ for (LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules : mIpv6ForwardingRules
+ .values()) {
+ for (Ipv6ForwardingRule rule : rules.values()) {
+ if (downstreamIfindex == rule.downstreamIfindex
+ && upstreamIfindex == rule.upstreamIfindex) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
@NonNull
private NetworkStats buildNetworkStats(@NonNull StatsType type, int ifIndex,
@NonNull final ForwardedStats diff) {
@@ -841,4 +1344,6 @@
final SparseArray<String> getInterfaceNamesForTesting() {
return mInterfaceNames;
}
+
+ private static native String[] getBpfCounterNames();
}
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfMap.java b/Tethering/src/com/android/networkstack/tethering/BpfMap.java
index 78d212c..9a9376f 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfMap.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfMap.java
@@ -41,6 +41,10 @@
* @param <V> the value of the map.
*/
public class BpfMap<K extends Struct, V extends Struct> implements AutoCloseable {
+ static {
+ System.loadLibrary("tetherutilsjni");
+ }
+
// Following definitions from kernel include/uapi/linux/bpf.h
public static final int BPF_F_RDWR = 0;
public static final int BPF_F_RDONLY = 1 << 3;
@@ -134,6 +138,11 @@
return deleteMapEntry(mMapFd, key.writeToBytes());
}
+ /** Returns {@code true} if this map contains no elements. */
+ public boolean isEmpty() throws ErrnoException {
+ return getFirstKey() == null;
+ }
+
private K getNextKeyInternal(@Nullable K key) throws ErrnoException {
final byte[] rawKey = getNextRawKey(
key == null ? null : key.writeToBytes());
@@ -213,7 +222,7 @@
}
@Override
- public void close() throws Exception {
+ public void close() throws ErrnoException {
closeMap(mMapFd);
}
diff --git a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
index bb7322f..b1e3cfe 100644
--- a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
+++ b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
@@ -418,7 +418,8 @@
if (period <= 0) return;
Intent intent = new Intent(ACTION_PROVISIONING_ALARM);
- mProvisioningRecheckAlarm = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ mProvisioningRecheckAlarm = PendingIntent.getBroadcast(mContext, 0, intent,
+ PendingIntent.FLAG_IMMUTABLE);
AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(
Context.ALARM_SERVICE);
long periodMs = period * MS_PER_HOUR;
diff --git a/Tethering/src/com/android/networkstack/tethering/Tether4Key.java b/Tethering/src/com/android/networkstack/tethering/Tether4Key.java
new file mode 100644
index 0000000..a01ea34
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/Tether4Key.java
@@ -0,0 +1,81 @@
+/*
+ * 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 com.android.networkstack.tethering;
+
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet4Address;
+import java.net.UnknownHostException;
+import java.util.Objects;
+
+/** Key type for downstream & upstream IPv4 forwarding maps. */
+public class Tether4Key extends Struct {
+ @Field(order = 0, type = Type.U32)
+ public final long iif;
+
+ @Field(order = 1, type = Type.EUI48)
+ public final MacAddress dstMac;
+
+ @Field(order = 2, type = Type.U8, padding = 1)
+ public final short l4proto;
+
+ @Field(order = 3, type = Type.ByteArray, arraysize = 4)
+ public final byte[] src4;
+
+ @Field(order = 4, type = Type.ByteArray, arraysize = 4)
+ public final byte[] dst4;
+
+ @Field(order = 5, type = Type.UBE16)
+ public final int srcPort;
+
+ @Field(order = 6, type = Type.UBE16)
+ public final int dstPort;
+
+ public Tether4Key(final long iif, @NonNull final MacAddress dstMac, final short l4proto,
+ final byte[] src4, final byte[] dst4, final int srcPort,
+ final int dstPort) {
+ Objects.requireNonNull(dstMac);
+
+ this.iif = iif;
+ this.dstMac = dstMac;
+ this.l4proto = l4proto;
+ this.src4 = src4;
+ this.dst4 = dst4;
+ this.srcPort = srcPort;
+ this.dstPort = dstPort;
+ }
+
+ @Override
+ public String toString() {
+ try {
+ return String.format(
+ "iif: %d, dstMac: %s, l4proto: %d, src4: %s, dst4: %s, "
+ + "srcPort: %d, dstPort: %d",
+ iif, dstMac, l4proto,
+ Inet4Address.getByAddress(src4), Inet4Address.getByAddress(dst4),
+ Short.toUnsignedInt((short) srcPort), Short.toUnsignedInt((short) dstPort));
+ } catch (UnknownHostException | IllegalArgumentException e) {
+ return String.format("Invalid IP address", e);
+ }
+ }
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tether4Value.java b/Tethering/src/com/android/networkstack/tethering/Tether4Value.java
new file mode 100644
index 0000000..03a226c
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/Tether4Value.java
@@ -0,0 +1,97 @@
+/*
+ * 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 com.android.networkstack.tethering;
+
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Objects;
+
+/** Value type for downstream & upstream IPv4 forwarding maps. */
+public class Tether4Value extends Struct {
+ @Field(order = 0, type = Type.U32)
+ public final long oif;
+
+ // The ethhdr struct which is defined in uapi/linux/if_ether.h
+ @Field(order = 1, type = Type.EUI48)
+ public final MacAddress ethDstMac;
+ @Field(order = 2, type = Type.EUI48)
+ public final MacAddress ethSrcMac;
+ @Field(order = 3, type = Type.UBE16)
+ public final int ethProto; // Packet type ID field.
+
+ @Field(order = 4, type = Type.U16)
+ public final int pmtu;
+
+ @Field(order = 5, type = Type.ByteArray, arraysize = 16)
+ public final byte[] src46;
+
+ @Field(order = 6, type = Type.ByteArray, arraysize = 16)
+ public final byte[] dst46;
+
+ @Field(order = 7, type = Type.UBE16)
+ public final int srcPort;
+
+ @Field(order = 8, type = Type.UBE16)
+ public final int dstPort;
+
+ // TODO: consider using U64.
+ @Field(order = 9, type = Type.U63)
+ public final long lastUsed;
+
+ public Tether4Value(final long oif, @NonNull final MacAddress ethDstMac,
+ @NonNull final MacAddress ethSrcMac, final int ethProto, final int pmtu,
+ final byte[] src46, final byte[] dst46, final int srcPort,
+ final int dstPort, final long lastUsed) {
+ Objects.requireNonNull(ethDstMac);
+ Objects.requireNonNull(ethSrcMac);
+
+ this.oif = oif;
+ this.ethDstMac = ethDstMac;
+ this.ethSrcMac = ethSrcMac;
+ this.ethProto = ethProto;
+ this.pmtu = pmtu;
+ this.src46 = src46;
+ this.dst46 = dst46;
+ this.srcPort = srcPort;
+ this.dstPort = dstPort;
+ this.lastUsed = lastUsed;
+ }
+
+ @Override
+ public String toString() {
+ try {
+ return String.format(
+ "oif: %d, ethDstMac: %s, ethSrcMac: %s, ethProto: %d, pmtu: %d, "
+ + "src46: %s, dst46: %s, srcPort: %d, dstPort: %d, "
+ + "lastUsed: %d",
+ oif, ethDstMac, ethSrcMac, ethProto, pmtu,
+ InetAddress.getByAddress(src46), InetAddress.getByAddress(dst46),
+ Short.toUnsignedInt((short) srcPort), Short.toUnsignedInt((short) dstPort),
+ lastUsed);
+ } catch (UnknownHostException | IllegalArgumentException e) {
+ return String.format("Invalid IP address", e);
+ }
+ }
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherIngressValue.java b/Tethering/src/com/android/networkstack/tethering/Tether6Value.java
similarity index 66%
rename from Tethering/src/com/android/networkstack/tethering/TetherIngressValue.java
rename to Tethering/src/com/android/networkstack/tethering/Tether6Value.java
index e2116fc..b3107fd 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetherIngressValue.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tether6Value.java
@@ -21,15 +21,13 @@
import androidx.annotation.NonNull;
import com.android.net.module.util.Struct;
-import com.android.net.module.util.Struct.Field;
-import com.android.net.module.util.Struct.Type;
import java.util.Objects;
-/** The value of BpfMap which is used for bpf offload. */
-public class TetherIngressValue extends Struct {
- @Field(order = 0, type = Type.U32)
- public final long oif; // The output interface index.
+/** Value type for downstream and upstream IPv6 forwarding maps. */
+public class Tether6Value extends Struct {
+ @Field(order = 0, type = Type.S32)
+ public final int oif; // The output interface index.
// The ethhdr struct which is defined in uapi/linux/if_ether.h
@Field(order = 1, type = Type.EUI48)
@@ -42,7 +40,7 @@
@Field(order = 4, type = Type.U16)
public final int pmtu; // The maximum L3 output path/route mtu.
- public TetherIngressValue(final long oif, @NonNull final MacAddress ethDstMac,
+ public Tether6Value(final int oif, @NonNull final MacAddress ethDstMac,
@NonNull final MacAddress ethSrcMac, final int ethProto, final int pmtu) {
Objects.requireNonNull(ethSrcMac);
Objects.requireNonNull(ethDstMac);
@@ -55,24 +53,6 @@
}
@Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
-
- if (!(obj instanceof TetherIngressValue)) return false;
-
- final TetherIngressValue that = (TetherIngressValue) obj;
-
- return oif == that.oif && ethDstMac.equals(that.ethDstMac)
- && ethSrcMac.equals(that.ethSrcMac) && ethProto == that.ethProto
- && pmtu == that.pmtu;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(oif, ethDstMac, ethSrcMac, ethProto, pmtu);
- }
-
- @Override
public String toString() {
return String.format("oif: %d, dstMac: %s, srcMac: %s, proto: %d, pmtu: %d", oif,
ethDstMac, ethSrcMac, ethProto, pmtu);
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherIngressKey.java b/Tethering/src/com/android/networkstack/tethering/TetherDownstream6Key.java
similarity index 86%
rename from Tethering/src/com/android/networkstack/tethering/TetherIngressKey.java
rename to Tethering/src/com/android/networkstack/tethering/TetherDownstream6Key.java
index 78683c5..3860cba 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetherIngressKey.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetherDownstream6Key.java
@@ -26,14 +26,14 @@
import java.util.Arrays;
/** The key of BpfMap which is used for bpf offload. */
-public class TetherIngressKey extends Struct {
+public class TetherDownstream6Key extends Struct {
@Field(order = 0, type = Type.U32)
public final long iif; // The input interface index.
@Field(order = 1, type = Type.ByteArray, arraysize = 16)
public final byte[] neigh6; // The destination IPv6 address.
- public TetherIngressKey(final long iif, final byte[] neigh6) {
+ public TetherDownstream6Key(final long iif, final byte[] neigh6) {
try {
final Inet6Address unused = (Inet6Address) InetAddress.getByAddress(neigh6);
} catch (ClassCastException | UnknownHostException e) {
@@ -48,9 +48,9 @@
public boolean equals(Object obj) {
if (this == obj) return true;
- if (!(obj instanceof TetherIngressKey)) return false;
+ if (!(obj instanceof TetherDownstream6Key)) return false;
- final TetherIngressKey that = (TetherIngressKey) obj;
+ final TetherDownstream6Key that = (TetherDownstream6Key) obj;
return iif == that.iif && Arrays.equals(neigh6, that.neigh6);
}
@@ -66,7 +66,7 @@
return String.format("iif: %d, neigh: %s", iif, Inet6Address.getByAddress(neigh6));
} catch (UnknownHostException e) {
// Should not happen because construtor already verify neigh6.
- throw new IllegalStateException("Invalid TetherIngressKey");
+ throw new IllegalStateException("Invalid TetherDownstream6Key");
}
}
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherUpstream6Key.java b/Tethering/src/com/android/networkstack/tethering/TetherUpstream6Key.java
new file mode 100644
index 0000000..c736f2a
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/TetherUpstream6Key.java
@@ -0,0 +1,29 @@
+/*
+ * 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 com.android.networkstack.tethering;
+
+import com.android.net.module.util.Struct;
+
+/** Key type for upstream IPv6 forwarding map. */
+public class TetherUpstream6Key extends Struct {
+ @Field(order = 0, type = Type.S32)
+ public final int iif; // The input interface index.
+
+ public TetherUpstream6Key(int iif) {
+ this.iif = iif;
+ }
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index fdd1c40..385c691 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -1636,6 +1636,13 @@
protected void handleNewUpstreamNetworkState(UpstreamNetworkState ns) {
mIPv6TetheringCoordinator.updateUpstreamNetworkState(ns);
mOffload.updateUpstreamNetworkState(ns);
+
+ // TODO: Delete all related offload rules which are using this upstream.
+ if (ns != null) {
+ // Add upstream index to the map. The upstream interface index is required while
+ // the conntrack event builds the offload rules.
+ mBpfCoordinator.addUpstreamIfindexToMap(ns.linkProperties);
+ }
}
private void handleInterfaceServingStateActive(int mode, IpServer who) {
@@ -2218,6 +2225,13 @@
&& !isProvisioningNeededButUnavailable();
}
+ private void dumpBpf(IndentingPrintWriter pw) {
+ pw.println("BPF offload:");
+ pw.increaseIndent();
+ mBpfCoordinator.dump(pw);
+ pw.decreaseIndent();
+ }
+
void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) {
// Binder.java closes the resource for us.
@SuppressWarnings("resource")
@@ -2228,6 +2242,11 @@
return;
}
+ if (argsContain(args, "bpf")) {
+ dumpBpf(pw);
+ return;
+ }
+
pw.println("Tethering:");
pw.increaseIndent();
@@ -2279,10 +2298,7 @@
mOffloadController.dump(pw);
pw.decreaseIndent();
- pw.println("BPF offload:");
- pw.increaseIndent();
- mBpfCoordinator.dump(pw);
- pw.decreaseIndent();
+ dumpBpf(pw);
pw.println("Private address coordinator:");
pw.increaseIndent();
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index 613328d..c69dc49 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -82,7 +82,6 @@
*/
@VisibleForTesting
public Tethering makeTethering(TetheringDependencies deps) {
- System.loadLibrary("tetherutilsjni");
return new Tethering(deps);
}
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index 42f83bf..f456273 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -70,6 +70,7 @@
test_config: "AndroidTest_Coverage.xml",
defaults: ["libnetworkstackutilsjni_deps"],
static_libs: [
+ "NetdStaticLibTestsLib",
"NetworkStaticLibTestsLib",
"NetworkStackTestsLib",
"TetheringTestsLib",
@@ -81,6 +82,7 @@
"libstaticjvmtiagent",
// For NetworkStackUtils included in NetworkStackBase
"libnetworkstackutilsjni",
+ "libtetherutilsjni",
],
jarjar_rules: ":TetheringTestsJarJarRules",
compile_multilib: "both",
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
index 04c1f00..cceaa8c 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
@@ -35,7 +35,6 @@
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
-import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -51,10 +50,12 @@
public final class BpfMapTest {
// Sync from packages/modules/Connectivity/Tethering/bpf_progs/offload.c.
private static final int TEST_MAP_SIZE = 16;
- private static final String TETHER_INGRESS_FS_PATH =
- "/sys/fs/bpf/map_test_tether_ingress_map";
+ private static final String TETHER_DOWNSTREAM6_FS_PATH =
+ "/sys/fs/bpf/tethering/map_test_tether_downstream6_map";
- private ArrayMap<TetherIngressKey, TetherIngressValue> mTestData;
+ private ArrayMap<TetherDownstream6Key, Tether6Value> mTestData;
+
+ private BpfMap<TetherDownstream6Key, Tether6Value> mTestMap;
@BeforeClass
public static void setupOnce() {
@@ -63,66 +64,54 @@
@Before
public void setUp() throws Exception {
- // TODO: Simply the test map creation and deletion.
- // - Make the map a class member (mTestMap)
- // - Open the test map RW in setUp
- // - Close the test map in tearDown.
- cleanTestMap();
-
mTestData = new ArrayMap<>();
- mTestData.put(createTetherIngressKey(101, "2001:db8::1"),
- createTetherIngressValue(11, "00:00:00:00:00:0a", "11:11:11:00:00:0b", ETH_P_IPV6,
- 1280));
- mTestData.put(createTetherIngressKey(102, "2001:db8::2"),
- createTetherIngressValue(22, "00:00:00:00:00:0c", "22:22:22:00:00:0d", ETH_P_IPV6,
- 1400));
- mTestData.put(createTetherIngressKey(103, "2001:db8::3"),
- createTetherIngressValue(33, "00:00:00:00:00:0e", "33:33:33:00:00:0f", ETH_P_IPV6,
- 1500));
+ mTestData.put(createTetherDownstream6Key(101, "2001:db8::1"),
+ createTether6Value(11, "00:00:00:00:00:0a", "11:11:11:00:00:0b",
+ ETH_P_IPV6, 1280));
+ mTestData.put(createTetherDownstream6Key(102, "2001:db8::2"),
+ createTether6Value(22, "00:00:00:00:00:0c", "22:22:22:00:00:0d",
+ ETH_P_IPV6, 1400));
+ mTestData.put(createTetherDownstream6Key(103, "2001:db8::3"),
+ createTether6Value(33, "00:00:00:00:00:0e", "33:33:33:00:00:0f",
+ ETH_P_IPV6, 1500));
+
+ initTestMap();
}
- @After
- public void tearDown() throws Exception {
- cleanTestMap();
+ private void initTestMap() throws Exception {
+ mTestMap = new BpfMap<>(
+ TETHER_DOWNSTREAM6_FS_PATH, BpfMap.BPF_F_RDWR,
+ TetherDownstream6Key.class, Tether6Value.class);
+
+ mTestMap.forEach((key, value) -> {
+ try {
+ assertTrue(mTestMap.deleteEntry(key));
+ } catch (ErrnoException e) {
+ fail("Fail to delete the key " + key + ": " + e);
+ }
+ });
+ assertNull(mTestMap.getFirstKey());
+ assertTrue(mTestMap.isEmpty());
}
- private BpfMap<TetherIngressKey, TetherIngressValue> getTestMap() throws Exception {
- return new BpfMap<>(
- TETHER_INGRESS_FS_PATH, BpfMap.BPF_F_RDWR,
- TetherIngressKey.class, TetherIngressValue.class);
- }
-
- private void cleanTestMap() throws Exception {
- try (BpfMap<TetherIngressKey, TetherIngressValue> bpfMap = getTestMap()) {
- bpfMap.forEach((key, value) -> {
- try {
- assertTrue(bpfMap.deleteEntry(key));
- } catch (ErrnoException e) {
- fail("Fail to delete the key " + key + ": " + e);
- }
- });
- assertNull(bpfMap.getFirstKey());
- }
- }
-
- private TetherIngressKey createTetherIngressKey(long iif, String address) throws Exception {
+ private TetherDownstream6Key createTetherDownstream6Key(long iif, String address)
+ throws Exception {
final InetAddress ipv6Address = InetAddress.getByName(address);
- return new TetherIngressKey(iif, ipv6Address.getAddress());
+ return new TetherDownstream6Key(iif, ipv6Address.getAddress());
}
- private TetherIngressValue createTetherIngressValue(long oif, String src, String dst, int proto,
- int pmtu) throws Exception {
+ private Tether6Value createTether6Value(int oif, String src, String dst, int proto, int pmtu) {
final MacAddress srcMac = MacAddress.fromString(src);
final MacAddress dstMac = MacAddress.fromString(dst);
- return new TetherIngressValue(oif, dstMac, srcMac, proto, pmtu);
+ return new Tether6Value(oif, dstMac, srcMac, proto, pmtu);
}
@Test
public void testGetFd() throws Exception {
- try (BpfMap readOnlyMap = new BpfMap<>(TETHER_INGRESS_FS_PATH, BpfMap.BPF_F_RDONLY,
- TetherIngressKey.class, TetherIngressValue.class)) {
+ try (BpfMap readOnlyMap = new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH, BpfMap.BPF_F_RDONLY,
+ TetherDownstream6Key.class, Tether6Value.class)) {
assertNotNull(readOnlyMap);
try {
readOnlyMap.insertEntry(mTestData.keyAt(0), mTestData.valueAt(0));
@@ -131,8 +120,8 @@
assertEquals(OsConstants.EPERM, expected.errno);
}
}
- try (BpfMap writeOnlyMap = new BpfMap<>(TETHER_INGRESS_FS_PATH, BpfMap.BPF_F_WRONLY,
- TetherIngressKey.class, TetherIngressValue.class)) {
+ try (BpfMap writeOnlyMap = new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH, BpfMap.BPF_F_WRONLY,
+ TetherDownstream6Key.class, Tether6Value.class)) {
assertNotNull(writeOnlyMap);
try {
writeOnlyMap.getFirstKey();
@@ -141,214 +130,212 @@
assertEquals(OsConstants.EPERM, expected.errno);
}
}
- try (BpfMap readWriteMap = new BpfMap<>(TETHER_INGRESS_FS_PATH, BpfMap.BPF_F_RDWR,
- TetherIngressKey.class, TetherIngressValue.class)) {
+ try (BpfMap readWriteMap = new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH, BpfMap.BPF_F_RDWR,
+ TetherDownstream6Key.class, Tether6Value.class)) {
assertNotNull(readWriteMap);
}
}
@Test
- public void testGetFirstKey() throws Exception {
- try (BpfMap<TetherIngressKey, TetherIngressValue> bpfMap = getTestMap()) {
- // getFirstKey on an empty map returns null.
- assertFalse(bpfMap.containsKey(mTestData.keyAt(0)));
- assertNull(bpfMap.getFirstKey());
- assertNull(bpfMap.getValue(mTestData.keyAt(0)));
+ public void testIsEmpty() throws Exception {
+ assertNull(mTestMap.getFirstKey());
+ assertTrue(mTestMap.isEmpty());
- // getFirstKey on a non-empty map returns the first key.
- bpfMap.insertEntry(mTestData.keyAt(0), mTestData.valueAt(0));
- assertEquals(mTestData.keyAt(0), bpfMap.getFirstKey());
- }
+ mTestMap.insertEntry(mTestData.keyAt(0), mTestData.valueAt(0));
+ assertFalse(mTestMap.isEmpty());
+
+ mTestMap.deleteEntry((mTestData.keyAt(0)));
+ assertTrue(mTestMap.isEmpty());
+ }
+
+ @Test
+ public void testGetFirstKey() throws Exception {
+ // getFirstKey on an empty map returns null.
+ assertFalse(mTestMap.containsKey(mTestData.keyAt(0)));
+ assertNull(mTestMap.getFirstKey());
+ assertNull(mTestMap.getValue(mTestData.keyAt(0)));
+
+ // getFirstKey on a non-empty map returns the first key.
+ mTestMap.insertEntry(mTestData.keyAt(0), mTestData.valueAt(0));
+ assertEquals(mTestData.keyAt(0), mTestMap.getFirstKey());
}
@Test
public void testGetNextKey() throws Exception {
- try (BpfMap<TetherIngressKey, TetherIngressValue> bpfMap = getTestMap()) {
- // [1] If the passed-in key is not found on empty map, return null.
- final TetherIngressKey nonexistentKey = createTetherIngressKey(1234, "2001:db8::10");
- assertNull(bpfMap.getNextKey(nonexistentKey));
+ // [1] If the passed-in key is not found on empty map, return null.
+ final TetherDownstream6Key nonexistentKey =
+ createTetherDownstream6Key(1234, "2001:db8::10");
+ assertNull(mTestMap.getNextKey(nonexistentKey));
- // [2] If the passed-in key is null on empty map, throw NullPointerException.
- try {
- bpfMap.getNextKey(null);
- fail("Getting next key with null key should throw NullPointerException");
- } catch (NullPointerException expected) { }
+ // [2] If the passed-in key is null on empty map, throw NullPointerException.
+ try {
+ mTestMap.getNextKey(null);
+ fail("Getting next key with null key should throw NullPointerException");
+ } catch (NullPointerException expected) { }
- // The BPF map has one entry now.
- final ArrayMap<TetherIngressKey, TetherIngressValue> resultMap = new ArrayMap<>();
- bpfMap.insertEntry(mTestData.keyAt(0), mTestData.valueAt(0));
- resultMap.put(mTestData.keyAt(0), mTestData.valueAt(0));
+ // The BPF map has one entry now.
+ final ArrayMap<TetherDownstream6Key, Tether6Value> resultMap =
+ new ArrayMap<>();
+ mTestMap.insertEntry(mTestData.keyAt(0), mTestData.valueAt(0));
+ resultMap.put(mTestData.keyAt(0), mTestData.valueAt(0));
- // [3] If the passed-in key is the last key, return null.
- // Because there is only one entry in the map, the first key equals the last key.
- final TetherIngressKey lastKey = bpfMap.getFirstKey();
- assertNull(bpfMap.getNextKey(lastKey));
+ // [3] If the passed-in key is the last key, return null.
+ // Because there is only one entry in the map, the first key equals the last key.
+ final TetherDownstream6Key lastKey = mTestMap.getFirstKey();
+ assertNull(mTestMap.getNextKey(lastKey));
- // The BPF map has two entries now.
- bpfMap.insertEntry(mTestData.keyAt(1), mTestData.valueAt(1));
- resultMap.put(mTestData.keyAt(1), mTestData.valueAt(1));
+ // The BPF map has two entries now.
+ mTestMap.insertEntry(mTestData.keyAt(1), mTestData.valueAt(1));
+ resultMap.put(mTestData.keyAt(1), mTestData.valueAt(1));
- // [4] If the passed-in key is found, return the next key.
- TetherIngressKey nextKey = bpfMap.getFirstKey();
- while (nextKey != null) {
- if (resultMap.remove(nextKey).equals(nextKey)) {
- fail("Unexpected result: " + nextKey);
- }
- nextKey = bpfMap.getNextKey(nextKey);
+ // [4] If the passed-in key is found, return the next key.
+ TetherDownstream6Key nextKey = mTestMap.getFirstKey();
+ while (nextKey != null) {
+ if (resultMap.remove(nextKey).equals(nextKey)) {
+ fail("Unexpected result: " + nextKey);
}
- assertTrue(resultMap.isEmpty());
-
- // [5] If the passed-in key is not found on non-empty map, return the first key.
- assertEquals(bpfMap.getFirstKey(), bpfMap.getNextKey(nonexistentKey));
-
- // [6] If the passed-in key is null on non-empty map, throw NullPointerException.
- try {
- bpfMap.getNextKey(null);
- fail("Getting next key with null key should throw NullPointerException");
- } catch (NullPointerException expected) { }
+ nextKey = mTestMap.getNextKey(nextKey);
}
+ assertTrue(resultMap.isEmpty());
+
+ // [5] If the passed-in key is not found on non-empty map, return the first key.
+ assertEquals(mTestMap.getFirstKey(), mTestMap.getNextKey(nonexistentKey));
+
+ // [6] If the passed-in key is null on non-empty map, throw NullPointerException.
+ try {
+ mTestMap.getNextKey(null);
+ fail("Getting next key with null key should throw NullPointerException");
+ } catch (NullPointerException expected) { }
}
@Test
public void testUpdateBpfMap() throws Exception {
- try (BpfMap<TetherIngressKey, TetherIngressValue> bpfMap = getTestMap()) {
+ final TetherDownstream6Key key = mTestData.keyAt(0);
+ final Tether6Value value = mTestData.valueAt(0);
+ final Tether6Value value2 = mTestData.valueAt(1);
+ assertFalse(mTestMap.deleteEntry(key));
- final TetherIngressKey key = mTestData.keyAt(0);
- final TetherIngressValue value = mTestData.valueAt(0);
- final TetherIngressValue value2 = mTestData.valueAt(1);
- assertFalse(bpfMap.deleteEntry(key));
+ // updateEntry will create an entry if it does not exist already.
+ mTestMap.updateEntry(key, value);
+ assertTrue(mTestMap.containsKey(key));
+ final Tether6Value result = mTestMap.getValue(key);
+ assertEquals(value, result);
- // updateEntry will create an entry if it does not exist already.
- bpfMap.updateEntry(key, value);
- assertTrue(bpfMap.containsKey(key));
- final TetherIngressValue result = bpfMap.getValue(key);
- assertEquals(value, result);
+ // updateEntry will update an entry that already exists.
+ mTestMap.updateEntry(key, value2);
+ assertTrue(mTestMap.containsKey(key));
+ final Tether6Value result2 = mTestMap.getValue(key);
+ assertEquals(value2, result2);
- // updateEntry will update an entry that already exists.
- bpfMap.updateEntry(key, value2);
- assertTrue(bpfMap.containsKey(key));
- final TetherIngressValue result2 = bpfMap.getValue(key);
- assertEquals(value2, result2);
-
- assertTrue(bpfMap.deleteEntry(key));
- assertFalse(bpfMap.containsKey(key));
- }
+ assertTrue(mTestMap.deleteEntry(key));
+ assertFalse(mTestMap.containsKey(key));
}
@Test
public void testInsertReplaceEntry() throws Exception {
- try (BpfMap<TetherIngressKey, TetherIngressValue> bpfMap = getTestMap()) {
+ final TetherDownstream6Key key = mTestData.keyAt(0);
+ final Tether6Value value = mTestData.valueAt(0);
+ final Tether6Value value2 = mTestData.valueAt(1);
- final TetherIngressKey key = mTestData.keyAt(0);
- final TetherIngressValue value = mTestData.valueAt(0);
- final TetherIngressValue value2 = mTestData.valueAt(1);
+ try {
+ mTestMap.replaceEntry(key, value);
+ fail("Replacing non-existent key " + key + " should throw NoSuchElementException");
+ } catch (NoSuchElementException expected) { }
+ assertFalse(mTestMap.containsKey(key));
- try {
- bpfMap.replaceEntry(key, value);
- fail("Replacing non-existent key " + key + " should throw NoSuchElementException");
- } catch (NoSuchElementException expected) { }
- assertFalse(bpfMap.containsKey(key));
+ mTestMap.insertEntry(key, value);
+ assertTrue(mTestMap.containsKey(key));
+ final Tether6Value result = mTestMap.getValue(key);
+ assertEquals(value, result);
+ try {
+ mTestMap.insertEntry(key, value);
+ fail("Inserting existing key " + key + " should throw IllegalStateException");
+ } catch (IllegalStateException expected) { }
- bpfMap.insertEntry(key, value);
- assertTrue(bpfMap.containsKey(key));
- final TetherIngressValue result = bpfMap.getValue(key);
- assertEquals(value, result);
- try {
- bpfMap.insertEntry(key, value);
- fail("Inserting existing key " + key + " should throw IllegalStateException");
- } catch (IllegalStateException expected) { }
-
- bpfMap.replaceEntry(key, value2);
- assertTrue(bpfMap.containsKey(key));
- final TetherIngressValue result2 = bpfMap.getValue(key);
- assertEquals(value2, result2);
- }
+ mTestMap.replaceEntry(key, value2);
+ assertTrue(mTestMap.containsKey(key));
+ final Tether6Value result2 = mTestMap.getValue(key);
+ assertEquals(value2, result2);
}
@Test
public void testIterateBpfMap() throws Exception {
- try (BpfMap<TetherIngressKey, TetherIngressValue> bpfMap = getTestMap()) {
- final ArrayMap<TetherIngressKey, TetherIngressValue> resultMap =
- new ArrayMap<>(mTestData);
+ final ArrayMap<TetherDownstream6Key, Tether6Value> resultMap =
+ new ArrayMap<>(mTestData);
- for (int i = 0; i < resultMap.size(); i++) {
- bpfMap.insertEntry(resultMap.keyAt(i), resultMap.valueAt(i));
- }
-
- bpfMap.forEach((key, value) -> {
- if (!value.equals(resultMap.remove(key))) {
- fail("Unexpected result: " + key + ", value: " + value);
- }
- });
- assertTrue(resultMap.isEmpty());
+ for (int i = 0; i < resultMap.size(); i++) {
+ mTestMap.insertEntry(resultMap.keyAt(i), resultMap.valueAt(i));
}
+
+ mTestMap.forEach((key, value) -> {
+ if (!value.equals(resultMap.remove(key))) {
+ fail("Unexpected result: " + key + ", value: " + value);
+ }
+ });
+ assertTrue(resultMap.isEmpty());
}
@Test
public void testIterateEmptyMap() throws Exception {
- try (BpfMap<TetherIngressKey, TetherIngressValue> bpfMap = getTestMap()) {
- // Can't use an int because variables used in a lambda must be final.
- final AtomicInteger count = new AtomicInteger();
- bpfMap.forEach((key, value) -> count.incrementAndGet());
- // Expect that the consumer was never called.
- assertEquals(0, count.get());
- }
+ // Can't use an int because variables used in a lambda must be final.
+ final AtomicInteger count = new AtomicInteger();
+ mTestMap.forEach((key, value) -> count.incrementAndGet());
+ // Expect that the consumer was never called.
+ assertEquals(0, count.get());
}
@Test
public void testIterateDeletion() throws Exception {
- try (BpfMap<TetherIngressKey, TetherIngressValue> bpfMap = getTestMap()) {
- final ArrayMap<TetherIngressKey, TetherIngressValue> resultMap =
- new ArrayMap<>(mTestData);
+ final ArrayMap<TetherDownstream6Key, Tether6Value> resultMap =
+ new ArrayMap<>(mTestData);
- for (int i = 0; i < resultMap.size(); i++) {
- bpfMap.insertEntry(resultMap.keyAt(i), resultMap.valueAt(i));
- }
-
- // Can't use an int because variables used in a lambda must be final.
- final AtomicInteger count = new AtomicInteger();
- bpfMap.forEach((key, value) -> {
- try {
- assertTrue(bpfMap.deleteEntry(key));
- } catch (ErrnoException e) {
- fail("Fail to delete key " + key + ": " + e);
- }
- if (!value.equals(resultMap.remove(key))) {
- fail("Unexpected result: " + key + ", value: " + value);
- }
- count.incrementAndGet();
- });
- assertEquals(3, count.get());
- assertTrue(resultMap.isEmpty());
- assertNull(bpfMap.getFirstKey());
+ for (int i = 0; i < resultMap.size(); i++) {
+ mTestMap.insertEntry(resultMap.keyAt(i), resultMap.valueAt(i));
}
+
+ // Can't use an int because variables used in a lambda must be final.
+ final AtomicInteger count = new AtomicInteger();
+ mTestMap.forEach((key, value) -> {
+ try {
+ assertTrue(mTestMap.deleteEntry(key));
+ } catch (ErrnoException e) {
+ fail("Fail to delete key " + key + ": " + e);
+ }
+ if (!value.equals(resultMap.remove(key))) {
+ fail("Unexpected result: " + key + ", value: " + value);
+ }
+ count.incrementAndGet();
+ });
+ assertEquals(3, count.get());
+ assertTrue(resultMap.isEmpty());
+ assertNull(mTestMap.getFirstKey());
}
@Test
public void testInsertOverflow() throws Exception {
- try (BpfMap<TetherIngressKey, TetherIngressValue> bpfMap = getTestMap()) {
- final ArrayMap<TetherIngressKey, TetherIngressValue> testData = new ArrayMap<>();
+ final ArrayMap<TetherDownstream6Key, Tether6Value> testData =
+ new ArrayMap<>();
- // Build test data for TEST_MAP_SIZE + 1 entries.
- for (int i = 1; i <= TEST_MAP_SIZE + 1; i++) {
- testData.put(createTetherIngressKey(i, "2001:db8::1"), createTetherIngressValue(
- 100, "de:ad:be:ef:00:01", "de:ad:be:ef:00:02", ETH_P_IPV6, 1500));
- }
+ // Build test data for TEST_MAP_SIZE + 1 entries.
+ for (int i = 1; i <= TEST_MAP_SIZE + 1; i++) {
+ testData.put(createTetherDownstream6Key(i, "2001:db8::1"),
+ createTether6Value(100, "de:ad:be:ef:00:01", "de:ad:be:ef:00:02",
+ ETH_P_IPV6, 1500));
+ }
- // Insert #TEST_MAP_SIZE test entries to the map. The map has reached the limit.
- for (int i = 0; i < TEST_MAP_SIZE; i++) {
- bpfMap.insertEntry(testData.keyAt(i), testData.valueAt(i));
- }
+ // Insert #TEST_MAP_SIZE test entries to the map. The map has reached the limit.
+ for (int i = 0; i < TEST_MAP_SIZE; i++) {
+ mTestMap.insertEntry(testData.keyAt(i), testData.valueAt(i));
+ }
- // The map won't allow inserting any more entries.
- try {
- bpfMap.insertEntry(testData.keyAt(TEST_MAP_SIZE), testData.valueAt(TEST_MAP_SIZE));
- fail("Writing too many entries should throw ErrnoException");
- } catch (ErrnoException expected) {
- // Expect that can't insert the entry anymore because the number of elements in the
- // map reached the limit. See man-pages/bpf.
- assertEquals(OsConstants.E2BIG, expected.errno);
- }
+ // The map won't allow inserting any more entries.
+ try {
+ mTestMap.insertEntry(testData.keyAt(TEST_MAP_SIZE), testData.valueAt(TEST_MAP_SIZE));
+ fail("Writing too many entries should throw ErrnoException");
+ } catch (ErrnoException expected) {
+ // Expect that can't insert the entry anymore because the number of elements in the
+ // map reached the limit. See man-pages/bpf.
+ assertEquals(OsConstants.E2BIG, expected.errno);
}
}
}
diff --git a/Tethering/tests/unit/Android.bp b/Tethering/tests/unit/Android.bp
index 5e4fe52..6c479a0 100644
--- a/Tethering/tests/unit/Android.bp
+++ b/Tethering/tests/unit/Android.bp
@@ -69,6 +69,7 @@
// For mockito extended
"libdexmakerjvmtiagent",
"libstaticjvmtiagent",
+ "libtetherutilsjni",
],
}
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 4763558..b45db7e 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -104,12 +104,15 @@
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
import com.android.networkstack.tethering.BpfMap;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
-import com.android.networkstack.tethering.TetherIngressKey;
-import com.android.networkstack.tethering.TetherIngressValue;
+import com.android.networkstack.tethering.Tether4Key;
+import com.android.networkstack.tethering.Tether4Value;
+import com.android.networkstack.tethering.Tether6Value;
+import com.android.networkstack.tethering.TetherDownstream6Key;
import com.android.networkstack.tethering.TetherLimitKey;
import com.android.networkstack.tethering.TetherLimitValue;
import com.android.networkstack.tethering.TetherStatsKey;
import com.android.networkstack.tethering.TetherStatsValue;
+import com.android.networkstack.tethering.TetherUpstream6Key;
import com.android.networkstack.tethering.TetheringConfiguration;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
@@ -172,7 +175,11 @@
@Mock private PrivateAddressCoordinator mAddressCoordinator;
@Mock private NetworkStatsManager mStatsManager;
@Mock private TetheringConfiguration mTetherConfig;
- @Mock private BpfMap<TetherIngressKey, TetherIngressValue> mBpfIngressMap;
+ @Mock private ConntrackMonitor mConntrackMonitor;
+ @Mock private BpfMap<Tether4Key, Tether4Value> mBpfDownstream4Map;
+ @Mock private BpfMap<Tether4Key, Tether4Value> mBpfUpstream4Map;
+ @Mock private BpfMap<TetherDownstream6Key, Tether6Value> mBpfDownstream6Map;
+ @Mock private BpfMap<TetherUpstream6Key, Tether6Value> mBpfUpstream6Map;
@Mock private BpfMap<TetherStatsKey, TetherStatsValue> mBpfStatsMap;
@Mock private BpfMap<TetherLimitKey, TetherLimitValue> mBpfLimitMap;
@@ -199,9 +206,6 @@
when(mDependencies.getInterfaceParams(UPSTREAM_IFACE)).thenReturn(UPSTREAM_IFACE_PARAMS);
when(mDependencies.getInterfaceParams(UPSTREAM_IFACE2)).thenReturn(UPSTREAM_IFACE_PARAMS2);
- when(mDependencies.getIfindex(eq(UPSTREAM_IFACE))).thenReturn(UPSTREAM_IFINDEX);
- when(mDependencies.getIfindex(eq(UPSTREAM_IFACE2))).thenReturn(UPSTREAM_IFINDEX2);
-
mInterfaceConfiguration = new InterfaceConfigurationParcel();
mInterfaceConfiguration.flags = new String[0];
if (interfaceType == TETHERING_BLUETOOTH) {
@@ -295,9 +299,30 @@
return mTetherConfig;
}
+ @NonNull
+ public ConntrackMonitor getConntrackMonitor(
+ ConntrackMonitor.ConntrackEventConsumer consumer) {
+ return mConntrackMonitor;
+ }
+
@Nullable
- public BpfMap<TetherIngressKey, TetherIngressValue> getBpfIngressMap() {
- return mBpfIngressMap;
+ public BpfMap<Tether4Key, Tether4Value> getBpfDownstream4Map() {
+ return mBpfDownstream4Map;
+ }
+
+ @Nullable
+ public BpfMap<Tether4Key, Tether4Value> getBpfUpstream4Map() {
+ return mBpfUpstream4Map;
+ }
+
+ @Nullable
+ public BpfMap<TetherDownstream6Key, Tether6Value> getBpfDownstream6Map() {
+ return mBpfDownstream6Map;
+ }
+
+ @Nullable
+ public BpfMap<TetherUpstream6Key, Tether6Value> getBpfUpstream6Map() {
+ return mBpfUpstream6Map;
}
@Nullable
@@ -770,15 +795,15 @@
}
@NonNull
- private static TetherIngressKey makeIngressKey(int upstreamIfindex,
+ private static TetherDownstream6Key makeDownstream6Key(int upstreamIfindex,
@NonNull final InetAddress dst) {
- return new TetherIngressKey(upstreamIfindex, dst.getAddress());
+ return new TetherDownstream6Key(upstreamIfindex, dst.getAddress());
}
@NonNull
- private static TetherIngressValue makeIngressValue(@NonNull final MacAddress dstMac) {
- return new TetherIngressValue(TEST_IFACE_PARAMS.index, dstMac, TEST_IFACE_PARAMS.macAddr,
- ETH_P_IPV6, NetworkStackConstants.ETHER_MTU);
+ private static Tether6Value makeDownstream6Value(@NonNull final MacAddress dstMac) {
+ return new Tether6Value(TEST_IFACE_PARAMS.index, dstMac,
+ TEST_IFACE_PARAMS.macAddr, ETH_P_IPV6, NetworkStackConstants.ETHER_MTU);
}
private <T> T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t) {
@@ -792,8 +817,8 @@
private void verifyTetherOffloadRuleAdd(@Nullable InOrder inOrder, int upstreamIfindex,
@NonNull final InetAddress dst, @NonNull final MacAddress dstMac) throws Exception {
if (mBpfDeps.isAtLeastS()) {
- verifyWithOrder(inOrder, mBpfIngressMap).updateEntry(
- makeIngressKey(upstreamIfindex, dst), makeIngressValue(dstMac));
+ verifyWithOrder(inOrder, mBpfDownstream6Map).updateEntry(
+ makeDownstream6Key(upstreamIfindex, dst), makeDownstream6Value(dstMac));
} else {
verifyWithOrder(inOrder, mNetd).tetherOffloadRuleAdd(matches(upstreamIfindex, dst,
dstMac));
@@ -803,8 +828,9 @@
private void verifyNeverTetherOffloadRuleAdd(int upstreamIfindex,
@NonNull final InetAddress dst, @NonNull final MacAddress dstMac) throws Exception {
if (mBpfDeps.isAtLeastS()) {
- verify(mBpfIngressMap, never()).updateEntry(makeIngressKey(upstreamIfindex, dst),
- makeIngressValue(dstMac));
+ verify(mBpfDownstream6Map, never()).updateEntry(
+ makeDownstream6Key(upstreamIfindex, dst),
+ makeDownstream6Value(dstMac));
} else {
verify(mNetd, never()).tetherOffloadRuleAdd(matches(upstreamIfindex, dst, dstMac));
}
@@ -812,7 +838,7 @@
private void verifyNeverTetherOffloadRuleAdd() throws Exception {
if (mBpfDeps.isAtLeastS()) {
- verify(mBpfIngressMap, never()).updateEntry(any(), any());
+ verify(mBpfDownstream6Map, never()).updateEntry(any(), any());
} else {
verify(mNetd, never()).tetherOffloadRuleAdd(any());
}
@@ -821,8 +847,8 @@
private void verifyTetherOffloadRuleRemove(@Nullable InOrder inOrder, int upstreamIfindex,
@NonNull final InetAddress dst, @NonNull final MacAddress dstMac) throws Exception {
if (mBpfDeps.isAtLeastS()) {
- verifyWithOrder(inOrder, mBpfIngressMap).deleteEntry(makeIngressKey(upstreamIfindex,
- dst));
+ verifyWithOrder(inOrder, mBpfDownstream6Map).deleteEntry(makeDownstream6Key(
+ upstreamIfindex, dst));
} else {
// |dstMac| is not required for deleting rules. Used bacause tetherOffloadRuleRemove
// uses a whole rule to be a argument.
@@ -834,12 +860,42 @@
private void verifyNeverTetherOffloadRuleRemove() throws Exception {
if (mBpfDeps.isAtLeastS()) {
- verify(mBpfIngressMap, never()).deleteEntry(any());
+ verify(mBpfDownstream6Map, never()).deleteEntry(any());
} else {
verify(mNetd, never()).tetherOffloadRuleRemove(any());
}
}
+ private void verifyStartUpstreamIpv6Forwarding(@Nullable InOrder inOrder, int upstreamIfindex)
+ throws Exception {
+ if (!mBpfDeps.isAtLeastS()) return;
+ final TetherUpstream6Key key = new TetherUpstream6Key(TEST_IFACE_PARAMS.index);
+ final Tether6Value value = new Tether6Value(upstreamIfindex,
+ MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS,
+ ETH_P_IPV6, NetworkStackConstants.ETHER_MTU);
+ verifyWithOrder(inOrder, mBpfUpstream6Map).insertEntry(key, value);
+ }
+
+ private void verifyStopUpstreamIpv6Forwarding(@Nullable InOrder inOrder)
+ throws Exception {
+ if (!mBpfDeps.isAtLeastS()) return;
+ final TetherUpstream6Key key = new TetherUpstream6Key(TEST_IFACE_PARAMS.index);
+ verifyWithOrder(inOrder, mBpfUpstream6Map).deleteEntry(key);
+ }
+
+ private void verifyNoUpstreamIpv6ForwardingChange(@Nullable InOrder inOrder) throws Exception {
+ if (!mBpfDeps.isAtLeastS()) return;
+ if (inOrder != null) {
+ inOrder.verify(mBpfUpstream6Map, never()).deleteEntry(any());
+ inOrder.verify(mBpfUpstream6Map, never()).insertEntry(any(), any());
+ inOrder.verify(mBpfUpstream6Map, never()).updateEntry(any(), any());
+ } else {
+ verify(mBpfUpstream6Map, never()).deleteEntry(any());
+ verify(mBpfUpstream6Map, never()).insertEntry(any(), any());
+ verify(mBpfUpstream6Map, never()).updateEntry(any(), any());
+ }
+ }
+
@NonNull
private static TetherStatsParcel buildEmptyTetherStatsParcel(int ifIndex) {
TetherStatsParcel parcel = new TetherStatsParcel();
@@ -848,12 +904,19 @@
}
private void resetNetdBpfMapAndCoordinator() throws Exception {
- reset(mNetd, mBpfIngressMap, mBpfCoordinator);
+ reset(mNetd, mBpfDownstream6Map, mBpfUpstream6Map, mBpfCoordinator);
+ // When the last rule is removed, tetherOffloadGetAndClearStats will log a WTF (and
+ // potentially crash the test) if the stats map is empty.
when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]);
when(mNetd.tetherOffloadGetAndClearStats(UPSTREAM_IFINDEX))
.thenReturn(buildEmptyTetherStatsParcel(UPSTREAM_IFINDEX));
when(mNetd.tetherOffloadGetAndClearStats(UPSTREAM_IFINDEX2))
.thenReturn(buildEmptyTetherStatsParcel(UPSTREAM_IFINDEX2));
+ // When the last rule is removed, tetherOffloadGetAndClearStats will log a WTF (and
+ // potentially crash the test) if the stats map is empty.
+ final TetherStatsValue allZeros = new TetherStatsValue(0, 0, 0, 0, 0, 0);
+ when(mBpfStatsMap.getValue(new TetherStatsKey(UPSTREAM_IFINDEX))).thenReturn(allZeros);
+ when(mBpfStatsMap.getValue(new TetherStatsKey(UPSTREAM_IFINDEX2))).thenReturn(allZeros);
}
@Test
@@ -864,7 +927,6 @@
final int myIfindex = TEST_IFACE_PARAMS.index;
final int notMyIfindex = myIfindex - 1;
- final MacAddress myMac = TEST_IFACE_PARAMS.macAddr;
final InetAddress neighA = InetAddresses.parseNumericAddress("2001:db8::1");
final InetAddress neighB = InetAddresses.parseNumericAddress("2001:db8::2");
final InetAddress neighLL = InetAddresses.parseNumericAddress("fe80::1");
@@ -874,33 +936,35 @@
final MacAddress macB = MacAddress.fromString("11:22:33:00:00:0b");
resetNetdBpfMapAndCoordinator();
- verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfIngressMap);
+ verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map, mBpfUpstream6Map);
// TODO: Perhaps verify the interaction of tetherOffloadSetInterfaceQuota and
// tetherOffloadGetAndClearStats in netd while the rules are changed.
// Events on other interfaces are ignored.
recvNewNeigh(notMyIfindex, neighA, NUD_REACHABLE, macA);
- verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfIngressMap);
+ verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map, mBpfUpstream6Map);
// Events on this interface are received and sent to netd.
recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
verify(mBpfCoordinator).tetherOffloadRuleAdd(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA));
verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neighA, macA);
+ verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX);
resetNetdBpfMapAndCoordinator();
recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
verify(mBpfCoordinator).tetherOffloadRuleAdd(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB));
verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neighB, macB);
+ verifyNoUpstreamIpv6ForwardingChange(null);
resetNetdBpfMapAndCoordinator();
// Link-local and multicast neighbors are ignored.
recvNewNeigh(myIfindex, neighLL, NUD_REACHABLE, macA);
- verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfIngressMap);
+ verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map, mBpfUpstream6Map);
recvNewNeigh(myIfindex, neighMC, NUD_REACHABLE, macA);
- verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfIngressMap);
+ verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map, mBpfUpstream6Map);
// A neighbor that is no longer valid causes the rule to be removed.
// NUD_FAILED events do not have a MAC address.
@@ -908,6 +972,7 @@
verify(mBpfCoordinator).tetherOffloadRuleRemove(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macNull));
verifyTetherOffloadRuleRemove(null, UPSTREAM_IFINDEX, neighA, macNull);
+ verifyNoUpstreamIpv6ForwardingChange(null);
resetNetdBpfMapAndCoordinator();
// A neighbor that is deleted causes the rule to be removed.
@@ -915,22 +980,27 @@
verify(mBpfCoordinator).tetherOffloadRuleRemove(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macNull));
verifyTetherOffloadRuleRemove(null, UPSTREAM_IFINDEX, neighB, macNull);
+ verifyStopUpstreamIpv6Forwarding(null);
resetNetdBpfMapAndCoordinator();
// Upstream changes result in updating the rules.
recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
+ verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX);
recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
resetNetdBpfMapAndCoordinator();
- InOrder inOrder = inOrder(mNetd, mBpfIngressMap);
+ InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfUpstream6Map);
LinkProperties lp = new LinkProperties();
lp.setInterfaceName(UPSTREAM_IFACE2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp, -1);
verify(mBpfCoordinator).tetherOffloadRuleUpdate(mIpServer, UPSTREAM_IFINDEX2);
verifyTetherOffloadRuleRemove(inOrder, UPSTREAM_IFINDEX, neighA, macA);
- verifyTetherOffloadRuleAdd(inOrder, UPSTREAM_IFINDEX2, neighA, macA);
verifyTetherOffloadRuleRemove(inOrder, UPSTREAM_IFINDEX, neighB, macB);
+ verifyStopUpstreamIpv6Forwarding(inOrder);
+ verifyTetherOffloadRuleAdd(inOrder, UPSTREAM_IFINDEX2, neighA, macA);
+ verifyStartUpstreamIpv6Forwarding(inOrder, UPSTREAM_IFINDEX2);
verifyTetherOffloadRuleAdd(inOrder, UPSTREAM_IFINDEX2, neighB, macB);
+ verifyNoUpstreamIpv6ForwardingChange(inOrder);
resetNetdBpfMapAndCoordinator();
// When the upstream is lost, rules are removed.
@@ -942,6 +1012,7 @@
verify(mBpfCoordinator, times(2)).tetherOffloadRuleClear(mIpServer);
verifyTetherOffloadRuleRemove(null, UPSTREAM_IFINDEX2, neighA, macA);
verifyTetherOffloadRuleRemove(null, UPSTREAM_IFINDEX2, neighB, macB);
+ verifyStopUpstreamIpv6Forwarding(inOrder);
resetNetdBpfMapAndCoordinator();
// If the upstream is IPv4-only, no rules are added.
@@ -950,7 +1021,8 @@
recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
// Clear function is called by #updateIpv6ForwardingRules for the IPv6 upstream is lost.
verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer);
- verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfIngressMap);
+ verifyNoUpstreamIpv6ForwardingChange(null);
+ verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map, mBpfUpstream6Map);
// Rules can be added again once upstream IPv6 connectivity is available.
lp.setInterfaceName(UPSTREAM_IFACE);
@@ -959,6 +1031,7 @@
verify(mBpfCoordinator).tetherOffloadRuleAdd(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB));
verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neighB, macB);
+ verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX);
verify(mBpfCoordinator, never()).tetherOffloadRuleAdd(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA));
verifyNeverTetherOffloadRuleAdd(UPSTREAM_IFINDEX, neighA, macA);
@@ -968,6 +1041,7 @@
dispatchTetherConnectionChanged(UPSTREAM_IFACE, null, 0);
verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer);
verifyTetherOffloadRuleRemove(null, UPSTREAM_IFINDEX, neighB, macB);
+ verifyStopUpstreamIpv6Forwarding(null);
// When the interface goes down, rules are removed.
lp.setInterfaceName(UPSTREAM_IFACE);
@@ -977,6 +1051,7 @@
verify(mBpfCoordinator).tetherOffloadRuleAdd(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA));
verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neighA, macA);
+ verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX);
verify(mBpfCoordinator).tetherOffloadRuleAdd(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB));
verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neighB, macB);
@@ -987,6 +1062,7 @@
verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer);
verifyTetherOffloadRuleRemove(null, UPSTREAM_IFINDEX, neighA, macA);
verifyTetherOffloadRuleRemove(null, UPSTREAM_IFINDEX, neighB, macB);
+ verifyStopUpstreamIpv6Forwarding(null);
verify(mIpNeighborMonitor).stop();
resetNetdBpfMapAndCoordinator();
}
@@ -1015,12 +1091,14 @@
verify(mBpfCoordinator).tetherOffloadRuleAdd(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neigh, macA));
verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neigh, macA);
+ verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX);
resetNetdBpfMapAndCoordinator();
recvDelNeigh(myIfindex, neigh, NUD_STALE, macA);
verify(mBpfCoordinator).tetherOffloadRuleRemove(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neigh, macNull));
verifyTetherOffloadRuleRemove(null, UPSTREAM_IFINDEX, neigh, macNull);
+ verifyStopUpstreamIpv6Forwarding(null);
resetNetdBpfMapAndCoordinator();
// [2] Disable BPF offload.
@@ -1032,11 +1110,13 @@
recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA);
verify(mBpfCoordinator, never()).tetherOffloadRuleAdd(any(), any());
verifyNeverTetherOffloadRuleAdd();
+ verifyNoUpstreamIpv6ForwardingChange(null);
resetNetdBpfMapAndCoordinator();
recvDelNeigh(myIfindex, neigh, NUD_STALE, macA);
verify(mBpfCoordinator, never()).tetherOffloadRuleRemove(any(), any());
verifyNeverTetherOffloadRuleRemove();
+ verifyNoUpstreamIpv6ForwardingChange(null);
resetNetdBpfMapAndCoordinator();
}
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 4abaf03..1270e50 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -56,6 +56,8 @@
import android.net.NetworkStats;
import android.net.TetherOffloadRuleParcel;
import android.net.TetherStatsParcel;
+import android.net.ip.ConntrackMonitor;
+import android.net.ip.ConntrackMonitor.ConntrackEventConsumer;
import android.net.ip.IpServer;
import android.net.util.SharedLog;
import android.os.Build;
@@ -153,8 +155,13 @@
@Mock private NetworkStatsManager mStatsManager;
@Mock private INetd mNetd;
@Mock private IpServer mIpServer;
+ @Mock private IpServer mIpServer2;
@Mock private TetheringConfiguration mTetherConfig;
- @Mock private BpfMap<TetherIngressKey, TetherIngressValue> mBpfIngressMap;
+ @Mock private ConntrackMonitor mConntrackMonitor;
+ @Mock private BpfMap<Tether4Key, Tether4Value> mBpfDownstream4Map;
+ @Mock private BpfMap<Tether4Key, Tether4Value> mBpfUpstream4Map;
+ @Mock private BpfMap<TetherDownstream6Key, Tether6Value> mBpfDownstream6Map;
+ @Mock private BpfMap<TetherUpstream6Key, Tether6Value> mBpfUpstream6Map;
// Late init since methods must be called by the thread that created this object.
private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb;
@@ -193,9 +200,29 @@
return mTetherConfig;
}
+ @NonNull
+ public ConntrackMonitor getConntrackMonitor(ConntrackEventConsumer consumer) {
+ return mConntrackMonitor;
+ }
+
@Nullable
- public BpfMap<TetherIngressKey, TetherIngressValue> getBpfIngressMap() {
- return mBpfIngressMap;
+ public BpfMap<Tether4Key, Tether4Value> getBpfDownstream4Map() {
+ return mBpfDownstream4Map;
+ }
+
+ @Nullable
+ public BpfMap<Tether4Key, Tether4Value> getBpfUpstream4Map() {
+ return mBpfUpstream4Map;
+ }
+
+ @Nullable
+ public BpfMap<TetherDownstream6Key, Tether6Value> getBpfDownstream6Map() {
+ return mBpfDownstream6Map;
+ }
+
+ @Nullable
+ public BpfMap<TetherUpstream6Key, Tether6Value> getBpfUpstream6Map() {
+ return mBpfUpstream6Map;
}
@Nullable
@@ -338,11 +365,41 @@
}
}
+ private void verifyStartUpstreamIpv6Forwarding(@Nullable InOrder inOrder, int downstreamIfIndex,
+ int upstreamIfindex) throws Exception {
+ if (!mDeps.isAtLeastS()) return;
+ final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfIndex);
+ final Tether6Value value = new Tether6Value(upstreamIfindex,
+ MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS,
+ ETH_P_IPV6, NetworkStackConstants.ETHER_MTU);
+ verifyWithOrder(inOrder, mBpfUpstream6Map).insertEntry(key, value);
+ }
+
+ private void verifyStopUpstreamIpv6Forwarding(@Nullable InOrder inOrder, int downstreamIfIndex)
+ throws Exception {
+ if (!mDeps.isAtLeastS()) return;
+ final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfIndex);
+ verifyWithOrder(inOrder, mBpfUpstream6Map).deleteEntry(key);
+ }
+
+ private void verifyNoUpstreamIpv6ForwardingChange(@Nullable InOrder inOrder) throws Exception {
+ if (!mDeps.isAtLeastS()) return;
+ if (inOrder != null) {
+ inOrder.verify(mBpfUpstream6Map, never()).deleteEntry(any());
+ inOrder.verify(mBpfUpstream6Map, never()).insertEntry(any(), any());
+ inOrder.verify(mBpfUpstream6Map, never()).updateEntry(any(), any());
+ } else {
+ verify(mBpfUpstream6Map, never()).deleteEntry(any());
+ verify(mBpfUpstream6Map, never()).insertEntry(any(), any());
+ verify(mBpfUpstream6Map, never()).updateEntry(any(), any());
+ }
+ }
+
private void verifyTetherOffloadRuleAdd(@Nullable InOrder inOrder,
@NonNull Ipv6ForwardingRule rule) throws Exception {
if (mDeps.isAtLeastS()) {
- verifyWithOrder(inOrder, mBpfIngressMap).updateEntry(
- rule.makeTetherIngressKey(), rule.makeTetherIngressValue());
+ verifyWithOrder(inOrder, mBpfDownstream6Map).updateEntry(
+ rule.makeTetherDownstream6Key(), rule.makeTether6Value());
} else {
verifyWithOrder(inOrder, mNetd).tetherOffloadRuleAdd(matches(rule));
}
@@ -350,7 +407,7 @@
private void verifyNeverTetherOffloadRuleAdd() throws Exception {
if (mDeps.isAtLeastS()) {
- verify(mBpfIngressMap, never()).updateEntry(any(), any());
+ verify(mBpfDownstream6Map, never()).updateEntry(any(), any());
} else {
verify(mNetd, never()).tetherOffloadRuleAdd(any());
}
@@ -359,7 +416,8 @@
private void verifyTetherOffloadRuleRemove(@Nullable InOrder inOrder,
@NonNull final Ipv6ForwardingRule rule) throws Exception {
if (mDeps.isAtLeastS()) {
- verifyWithOrder(inOrder, mBpfIngressMap).deleteEntry(rule.makeTetherIngressKey());
+ verifyWithOrder(inOrder, mBpfDownstream6Map).deleteEntry(
+ rule.makeTetherDownstream6Key());
} else {
verifyWithOrder(inOrder, mNetd).tetherOffloadRuleRemove(matches(rule));
}
@@ -367,7 +425,7 @@
private void verifyNeverTetherOffloadRuleRemove() throws Exception {
if (mDeps.isAtLeastS()) {
- verify(mBpfIngressMap, never()).deleteEntry(any());
+ verify(mBpfDownstream6Map, never()).deleteEntry(any());
} else {
verify(mNetd, never()).tetherOffloadRuleRemove(any());
}
@@ -435,7 +493,7 @@
// BpfCoordinator#tetherOffloadRuleAdd and BpfCoordinator#tetherOffloadGetAndClearStats.
// The #verifyTetherOffloadGetAndClearStats can't distinguish who has ever called
// mBpfStatsMap#getValue and get a wrong calling count which counts all.
- final InOrder inOrder = inOrder(mNetd, mBpfIngressMap, mBpfLimitMap, mBpfStatsMap);
+ final InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfLimitMap, mBpfStatsMap);
final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A);
coordinator.tetherOffloadRuleAdd(mIpServer, rule);
verifyTetherOffloadRuleAdd(inOrder, rule);
@@ -651,11 +709,11 @@
}
@Test
- public void testRuleMakeTetherIngressKey() throws Exception {
+ public void testRuleMakeTetherDownstream6Key() throws Exception {
final Integer mobileIfIndex = 100;
final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A);
- final TetherIngressKey key = rule.makeTetherIngressKey();
+ final TetherDownstream6Key key = rule.makeTetherDownstream6Key();
assertEquals(key.iif, (long) mobileIfIndex);
assertTrue(Arrays.equals(key.neigh6, NEIGH_A.getAddress()));
// iif (4) + neigh6 (16) = 20.
@@ -663,11 +721,11 @@
}
@Test
- public void testRuleMakeTetherIngressValue() throws Exception {
+ public void testRuleMakeTether6Value() throws Exception {
final Integer mobileIfIndex = 100;
final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A);
- final TetherIngressValue value = rule.makeTetherIngressValue();
+ final Tether6Value value = rule.makeTether6Value();
assertEquals(value.oif, DOWNSTREAM_IFINDEX);
assertEquals(value.ethDstMac, MAC_A);
assertEquals(value.ethSrcMac, DOWNSTREAM_MAC);
@@ -691,7 +749,7 @@
// Set the unlimited quota as default if the service has never applied a data limit for a
// given upstream. Note that the data limit only be applied on an upstream which has rules.
final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A);
- final InOrder inOrder = inOrder(mNetd, mBpfIngressMap, mBpfLimitMap, mBpfStatsMap);
+ final InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfLimitMap, mBpfStatsMap);
coordinator.tetherOffloadRuleAdd(mIpServer, rule);
verifyTetherOffloadRuleAdd(inOrder, rule);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED,
@@ -734,7 +792,7 @@
// Applying a data limit to the current upstream does not take any immediate action.
// The data limit could be only set on an upstream which has rules.
final long limit = 12345;
- final InOrder inOrder = inOrder(mNetd, mBpfIngressMap, mBpfLimitMap, mBpfStatsMap);
+ final InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfLimitMap, mBpfStatsMap);
mTetherStatsProvider.onSetLimit(mobileIface, limit);
waitForIdle();
verifyNeverTetherOffloadSetInterfaceQuota(inOrder);
@@ -779,7 +837,8 @@
coordinator.addUpstreamNameToLookupTable(ethIfIndex, ethIface);
coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface);
- final InOrder inOrder = inOrder(mNetd, mBpfIngressMap, mBpfLimitMap, mBpfStatsMap);
+ final InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfUpstream6Map, mBpfLimitMap,
+ mBpfStatsMap);
// Before the rule test, here are the additional actions while the rules are changed.
// - After adding the first rule on a given upstream, the coordinator adds a data limit.
@@ -799,7 +858,7 @@
verifyTetherOffloadRuleAdd(inOrder, ethernetRuleA);
verifyTetherOffloadSetInterfaceQuota(inOrder, ethIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
-
+ verifyStartUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, ethIfIndex);
coordinator.tetherOffloadRuleAdd(mIpServer, ethernetRuleB);
verifyTetherOffloadRuleAdd(inOrder, ethernetRuleB);
@@ -815,11 +874,13 @@
// by one for updating upstream interface index by #tetherOffloadRuleUpdate.
coordinator.tetherOffloadRuleUpdate(mIpServer, mobileIfIndex);
verifyTetherOffloadRuleRemove(inOrder, ethernetRuleA);
+ verifyTetherOffloadRuleRemove(inOrder, ethernetRuleB);
+ verifyStopUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX);
+ verifyTetherOffloadGetAndClearStats(inOrder, ethIfIndex);
verifyTetherOffloadRuleAdd(inOrder, mobileRuleA);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
- verifyTetherOffloadRuleRemove(inOrder, ethernetRuleB);
- verifyTetherOffloadGetAndClearStats(inOrder, ethIfIndex);
+ verifyStartUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, mobileIfIndex);
verifyTetherOffloadRuleAdd(inOrder, mobileRuleB);
// [3] Clear all rules for a given IpServer.
@@ -828,6 +889,7 @@
coordinator.tetherOffloadRuleClear(mIpServer);
verifyTetherOffloadRuleRemove(inOrder, mobileRuleA);
verifyTetherOffloadRuleRemove(inOrder, mobileRuleB);
+ verifyStopUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX);
verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex);
// [4] Force pushing stats update to verify that the last diff of stats is reported on all
@@ -845,7 +907,7 @@
private void checkBpfDisabled() throws Exception {
// The caller may mock the global dependencies |mDeps| which is used in
// #makeBpfCoordinator for testing.
- // See #testBpfDisabledbyNoBpfIngressMap.
+ // See #testBpfDisabledbyNoBpfDownstream6Map.
final BpfCoordinator coordinator = makeBpfCoordinator();
coordinator.startPolling();
@@ -908,9 +970,18 @@
@Test
@IgnoreUpTo(Build.VERSION_CODES.R)
- public void testBpfDisabledbyNoBpfIngressMap() throws Exception {
+ public void testBpfDisabledbyNoBpfDownstream6Map() throws Exception {
setupFunctioningNetdInterface();
- doReturn(null).when(mDeps).getBpfIngressMap();
+ doReturn(null).when(mDeps).getBpfDownstream6Map();
+
+ checkBpfDisabled();
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testBpfDisabledbyNoBpfUpstream6Map() throws Exception {
+ setupFunctioningNetdInterface();
+ doReturn(null).when(mDeps).getBpfUpstream6Map();
checkBpfDisabled();
}
@@ -981,4 +1052,48 @@
waitForIdle();
verifyTetherOffloadGetStats();
}
+
+ @Test
+ public void testStartStopConntrackMonitoring() throws Exception {
+ setupFunctioningNetdInterface();
+
+ final BpfCoordinator coordinator = makeBpfCoordinator();
+
+ // [1] Don't stop monitoring if it has never started.
+ coordinator.stopMonitoring(mIpServer);
+ verify(mConntrackMonitor, never()).start();
+
+ // [2] Start monitoring.
+ coordinator.startMonitoring(mIpServer);
+ verify(mConntrackMonitor).start();
+ clearInvocations(mConntrackMonitor);
+
+ // [3] Stop monitoring.
+ coordinator.stopMonitoring(mIpServer);
+ verify(mConntrackMonitor).stop();
+ }
+
+ @Test
+ public void testStartStopConntrackMonitoringWithTwoDownstreamIfaces() throws Exception {
+ setupFunctioningNetdInterface();
+
+ final BpfCoordinator coordinator = makeBpfCoordinator();
+
+ // [1] Start monitoring at the first IpServer adding.
+ coordinator.startMonitoring(mIpServer);
+ verify(mConntrackMonitor).start();
+ clearInvocations(mConntrackMonitor);
+
+ // [2] Don't start monitoring at the second IpServer adding.
+ coordinator.startMonitoring(mIpServer2);
+ verify(mConntrackMonitor, never()).start();
+
+ // [3] Don't stop monitoring if any downstream interface exists.
+ coordinator.stopMonitoring(mIpServer2);
+ verify(mConntrackMonitor, never()).stop();
+
+ // [4] Stop monitoring if no downstream exists.
+ coordinator.stopMonitoring(mIpServer);
+ verify(mConntrackMonitor).stop();
+ }
}
diff --git a/framework/Android.bp b/framework/Android.bp
deleted file mode 100644
index 8db8d76..0000000
--- a/framework/Android.bp
+++ /dev/null
@@ -1,29 +0,0 @@
-//
-// 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.
-//
-
-// TODO: use a java_library in the bootclasspath instead
-filegroup {
- name: "framework-connectivity-sources",
- srcs: [
- "src/**/*.java",
- "src/**/*.aidl",
- ],
- path: "src",
- visibility: [
- "//frameworks/base",
- "//packages/modules/Connectivity:__subpackages__",
- ],
-}
\ No newline at end of file
diff --git a/framework/src/com/android/connectivity/aidl/INetworkAgent.aidl b/framework/src/com/android/connectivity/aidl/INetworkAgent.aidl
deleted file mode 100644
index 1af9e76..0000000
--- a/framework/src/com/android/connectivity/aidl/INetworkAgent.aidl
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * 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 perNmissions and
- * limitations under the License.
- */
-package com.android.connectivity.aidl;
-
-import android.net.NattKeepalivePacketData;
-import android.net.TcpKeepalivePacketData;
-
-import com.android.connectivity.aidl.INetworkAgentRegistry;
-
-/**
- * Interface to notify NetworkAgent of connectivity events.
- * @hide
- */
-oneway interface INetworkAgent {
- void onRegistered(in INetworkAgentRegistry registry);
- void onDisconnected();
- void onBandwidthUpdateRequested();
- void onValidationStatusChanged(int validationStatus,
- in @nullable String captivePortalUrl);
- void onSaveAcceptUnvalidated(boolean acceptUnvalidated);
- void onStartNattSocketKeepalive(int slot, int intervalDurationMs,
- in NattKeepalivePacketData packetData);
- void onStartTcpSocketKeepalive(int slot, int intervalDurationMs,
- in TcpKeepalivePacketData packetData);
- void onStopSocketKeepalive(int slot);
- void onSignalStrengthThresholdsUpdated(in int[] thresholds);
- void onPreventAutomaticReconnect();
- void onAddNattKeepalivePacketFilter(int slot,
- in NattKeepalivePacketData packetData);
- void onAddTcpKeepalivePacketFilter(int slot,
- in TcpKeepalivePacketData packetData);
- void onRemoveKeepalivePacketFilter(int slot);
-}
diff --git a/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl b/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl
deleted file mode 100644
index d42a340..0000000
--- a/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * 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 perNmissions and
- * limitations under the License.
- */
-package com.android.connectivity.aidl;
-
-import android.net.LinkProperties;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-
-/**
- * Interface for NetworkAgents to send network network properties.
- * @hide
- */
-oneway interface INetworkAgentRegistry {
- void sendNetworkCapabilities(in NetworkCapabilities nc);
- void sendLinkProperties(in LinkProperties lp);
- // TODO: consider replacing this by "markConnected()" and removing
- void sendNetworkInfo(in NetworkInfo info);
- void sendScore(int score);
- void sendExplicitlySelected(boolean explicitlySelected, boolean acceptPartial);
- void sendSocketKeepaliveEvent(int slot, int reason);
- void sendUnderlyingNetworks(in @nullable List<Network> networks);
-}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java
index 8fadf9e..5c99c67 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java
@@ -15,21 +15,20 @@
*/
package com.android.cts.net.hostside;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.resetMeteredNetwork;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setupMeteredNetwork;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setupActiveNetworkMeteredness;
import static com.android.cts.net.hostside.Property.METERED_NETWORK;
import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
import android.util.ArraySet;
-import android.util.Pair;
import com.android.compatibility.common.util.BeforeAfterRule;
+import com.android.compatibility.common.util.ThrowingRunnable;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
public class MeterednessConfigurationRule extends BeforeAfterRule {
- private Pair<String, Boolean> mSsidAndInitialMeteredness;
+ private ThrowingRunnable mMeterednessResetter;
@Override
public void onBefore(Statement base, Description description) throws Throwable {
@@ -48,13 +47,13 @@
}
public void configureNetworkMeteredness(boolean metered) throws Exception {
- mSsidAndInitialMeteredness = setupMeteredNetwork(metered);
+ mMeterednessResetter = setupActiveNetworkMeteredness(metered);
}
public void resetNetworkMeteredness() throws Exception {
- if (mSsidAndInitialMeteredness != null) {
- resetMeteredNetwork(mSsidAndInitialMeteredness.first,
- mSsidAndInitialMeteredness.second);
+ if (mMeterednessResetter != null) {
+ mMeterednessResetter.run();
+ mMeterednessResetter = null;
}
}
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
index 2ac29e7..955317b 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
@@ -17,16 +17,13 @@
package com.android.cts.net.hostside;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isActiveNetworkMetered;
import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
@@ -186,7 +183,7 @@
public void setUp() throws Exception {
super.setUp();
- assumeTrue(isActiveNetworkMetered(true) || canChangeActiveNetworkMeteredness());
+ assumeTrue(canChangeActiveNetworkMeteredness());
registerBroadcastReceiver();
@@ -198,13 +195,13 @@
setBatterySaverMode(false);
setRestrictBackground(false);
- // Make wifi a metered network.
+ // Mark network as metered.
mMeterednessConfiguration.configureNetworkMeteredness(true);
// Register callback
registerNetworkCallback((INetworkCallback.Stub) mTestNetworkCallback);
- // Once the wifi is marked as metered, the wifi will reconnect. Wait for onAvailable()
- // callback to ensure wifi is connected before the test and store the default network.
+ // Wait for onAvailable() callback to ensure network is available before the test
+ // and store the default network.
mNetwork = mTestNetworkCallback.expectAvailableCallbackAndGetNetwork();
// Check that the network is metered.
mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
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 3041dfa..b61535b 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
@@ -20,6 +20,7 @@
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
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 com.android.compatibility.common.util.SystemUtil.runShellCommand;
@@ -28,6 +29,7 @@
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;
import static org.junit.Assert.fail;
@@ -40,25 +42,36 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.wifi.WifiManager;
+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 android.util.Pair;
+
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.AppStandbyUtils;
import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.ThrowingRunnable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import androidx.test.platform.app.InstrumentationRegistry;
-
public class NetworkPolicyTestUtils {
+ // android.telephony.CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS
+ // TODO: Expose it as a @TestApi instead of copying the constant
+ private static final String KEY_CARRIER_METERED_APN_TYPES_STRINGS =
+ "carrier_metered_apn_types_strings";
+
private static final int TIMEOUT_CHANGE_METEREDNESS_MS = 10_000;
private static ConnectivityManager mCm;
private static WifiManager mWm;
+ private static CarrierConfigManager mCarrierConfigManager;
private static Boolean mBatterySaverSupported;
private static Boolean mDataSaverSupported;
@@ -135,16 +148,40 @@
}
public static boolean canChangeActiveNetworkMeteredness() {
- final Network activeNetwork = getConnectivityManager().getActiveNetwork();
- final NetworkCapabilities networkCapabilities
- = getConnectivityManager().getNetworkCapabilities(activeNetwork);
- return networkCapabilities.hasTransport(TRANSPORT_WIFI);
+ final NetworkCapabilities networkCapabilities = getActiveNetworkCapabilities();
+ return networkCapabilities.hasTransport(TRANSPORT_WIFI)
+ || networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
}
- public static Pair<String, Boolean> setupMeteredNetwork(boolean metered) throws Exception {
+ /**
+ * Updates the meteredness of the active network. Right now we can only change meteredness
+ * of either Wifi or cellular network, so if the active network is not either of these, this
+ * will throw an exception.
+ *
+ * @return a {@link ThrowingRunnable} object that can used to reset the meteredness change
+ * made by this method.
+ */
+ public static ThrowingRunnable setupActiveNetworkMeteredness(boolean metered) throws Exception {
if (isActiveNetworkMetered(metered)) {
return null;
}
+ final NetworkCapabilities networkCapabilities = getActiveNetworkCapabilities();
+ if (networkCapabilities.hasTransport(TRANSPORT_WIFI)) {
+ final String ssid = getWifiSsid();
+ setWifiMeteredStatus(ssid, metered);
+ return () -> setWifiMeteredStatus(ssid, !metered);
+ } else if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+ final int subId = SubscriptionManager.getActiveDataSubscriptionId();
+ setCellularMeteredStatus(subId, metered);
+ return () -> setCellularMeteredStatus(subId, !metered);
+ } else {
+ // Right now, we don't have a way to change meteredness of networks other
+ // than Wi-Fi or Cellular, so just throw an exception.
+ throw new IllegalStateException("Can't change meteredness of current active network");
+ }
+ }
+
+ private static String getWifiSsid() {
final boolean isLocationEnabled = isLocationEnabled();
try {
if (!isLocationEnabled) {
@@ -152,8 +189,7 @@
}
final String ssid = unquoteSSID(getWifiManager().getConnectionInfo().getSSID());
assertNotEquals(WifiManager.UNKNOWN_SSID, ssid);
- setWifiMeteredStatus(ssid, metered);
- return Pair.create(ssid, !metered);
+ return ssid;
} finally {
// Reset the location enabled state
if (!isLocationEnabled) {
@@ -162,11 +198,13 @@
}
}
- public static void resetMeteredNetwork(String ssid, boolean metered) throws Exception {
- setWifiMeteredStatus(ssid, metered);
+ private static NetworkCapabilities getActiveNetworkCapabilities() {
+ final Network activeNetwork = getConnectivityManager().getActiveNetwork();
+ assertNotNull("No active network available", activeNetwork);
+ return getConnectivityManager().getNetworkCapabilities(activeNetwork);
}
- public static void setWifiMeteredStatus(String ssid, boolean metered) throws Exception {
+ 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);
@@ -174,15 +212,24 @@
assertActiveNetworkMetered(metered);
}
- public static void assertWifiMeteredStatus(String ssid, boolean expectedMeteredStatus) {
+ 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 void setCellularMeteredStatus(int subId, boolean metered) throws Exception {
+ final PersistableBundle bundle = new PersistableBundle();
+ bundle.putStringArray(KEY_CARRIER_METERED_APN_TYPES_STRINGS,
+ new String[] {ApnSetting.TYPE_MMS_STRING});
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(getCarrierConfigManager(),
+ (cm) -> cm.overrideConfig(subId, metered ? null : bundle));
+ assertActiveNetworkMetered(metered);
+ }
+
// Copied from cts/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
- public static void assertActiveNetworkMetered(boolean expectedMeteredStatus) throws Exception {
+ private static void assertActiveNetworkMetered(boolean expectedMeteredStatus) throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final NetworkCallback networkCallback = new NetworkCallback() {
@Override
@@ -197,12 +244,15 @@
// with the current setting. Therefore, if the setting has already been changed,
// this method will return right away, and if not it will wait for the setting to change.
getConnectivityManager().registerDefaultNetworkCallback(networkCallback);
- 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());
+ 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());
+ }
+ } finally {
+ getConnectivityManager().unregisterNetworkCallback(networkCallback);
}
- getConnectivityManager().unregisterNetworkCallback(networkCallback);
}
public static void setRestrictBackground(boolean enabled) {
@@ -274,6 +324,14 @@
return mWm;
}
+ public static CarrierConfigManager getCarrierConfigManager() {
+ if (mCarrierConfigManager == null) {
+ mCarrierConfigManager = (CarrierConfigManager) getContext().getSystemService(
+ Context.CARRIER_CONFIG_SERVICE);
+ }
+ return mCarrierConfigManager;
+ }
+
public static Context getContext() {
return getInstrumentation().getContext();
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index 81a431c..a663cd6 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -16,6 +16,8 @@
package com.android.cts.net.hostside;
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.os.Process.INVALID_UID;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
@@ -25,6 +27,9 @@
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.POLLIN;
import static android.system.OsConstants.SOCK_DGRAM;
+import static android.test.MoreAsserts.assertNotEqual;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import android.annotation.Nullable;
import android.app.DownloadManager;
@@ -45,9 +50,14 @@
import android.net.NetworkRequest;
import android.net.Proxy;
import android.net.ProxyInfo;
+import android.net.TransportInfo;
import android.net.Uri;
+import android.net.VpnManager;
import android.net.VpnService;
+import android.net.VpnTransportInfo;
import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.SystemProperties;
@@ -687,6 +697,34 @@
setAndVerifyPrivateDns(initialMode);
}
+ private class NeverChangeNetworkCallback extends NetworkCallback {
+ private CountDownLatch mLatch = new CountDownLatch(1);
+ private volatile Network mFirstNetwork;
+ private volatile Network mOtherNetwork;
+
+ public void onAvailable(Network n) {
+ // Don't assert here, as it crashes the test with a hard to debug message.
+ if (mFirstNetwork == null) {
+ mFirstNetwork = n;
+ mLatch.countDown();
+ } else if (mOtherNetwork == null) {
+ mOtherNetwork = n;
+ }
+ }
+
+ public Network getFirstNetwork() throws Exception {
+ assertTrue(
+ "System default callback got no network after " + TIMEOUT_MS + "ms. "
+ + "Please ensure the device has a working Internet connection.",
+ mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ return mFirstNetwork;
+ }
+
+ public void assertNeverChanged() {
+ assertNull(mOtherNetwork);
+ }
+ }
+
public void testDefault() throws Exception {
if (!supportedHardware()) return;
// If adb TCP port opened, this test may running by adb over network.
@@ -702,6 +740,14 @@
getInstrumentation().getTargetContext(), MyVpnService.ACTION_ESTABLISHED);
receiver.register();
+
+ // Expect the system default network not to change.
+ final NeverChangeNetworkCallback neverChangeCallback = new NeverChangeNetworkCallback();
+ final Network defaultNetwork = mCM.getActiveNetwork();
+ runWithShellPermissionIdentity(() ->
+ mCM.registerSystemDefaultNetworkCallback(neverChangeCallback,
+ new Handler(Looper.getMainLooper())), NETWORK_SETTINGS);
+
FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
@@ -719,6 +765,20 @@
checkTrafficOnVpn();
+ expectVpnTransportInfo(mCM.getActiveNetwork());
+
+ // Check that system default network callback has not seen any network changes, even though
+ // the app's default network changed. This needs to be done before testing private
+ // DNS because checkStrictModePrivateDns will set the private DNS server to a nonexistent
+ // name, which will cause validation to fail and cause the default network to switch (e.g.,
+ // from wifi to cellular).
+ assertEquals(defaultNetwork, neverChangeCallback.getFirstNetwork());
+ assertNotEqual(defaultNetwork, mCM.getActiveNetwork());
+ neverChangeCallback.assertNeverChanged();
+ runWithShellPermissionIdentity(
+ () -> mCM.unregisterNetworkCallback(neverChangeCallback),
+ NETWORK_SETTINGS);
+
checkStrictModePrivateDns();
receiver.unregisterQuietly();
@@ -739,6 +799,8 @@
checkTrafficOnVpn();
+ expectVpnTransportInfo(mCM.getActiveNetwork());
+
checkStrictModePrivateDns();
}
@@ -764,6 +826,10 @@
assertSocketStillOpen(remoteFd, TEST_HOST);
checkNoTrafficOnVpn();
+
+ final Network network = mCM.getActiveNetwork();
+ final NetworkCapabilities nc = mCM.getNetworkCapabilities(network);
+ assertFalse(nc.hasTransport(TRANSPORT_VPN));
}
public void testGetConnectionOwnerUidSecurity() throws Exception {
@@ -778,8 +844,11 @@
InetSocketAddress rem = new InetSocketAddress(s.getInetAddress(), s.getPort());
try {
int uid = mCM.getConnectionOwnerUid(OsConstants.IPPROTO_TCP, loc, rem);
- fail("Only an active VPN app may call this API.");
- } catch (SecurityException expected) {
+ assertEquals("Only an active VPN app should see connection information",
+ INVALID_UID, uid);
+ } catch (SecurityException acceptable) {
+ // R and below throw SecurityException if a non-active VPN calls this method.
+ // As long as we can't actually get socket information, either behaviour is fine.
return;
}
}
@@ -918,6 +987,8 @@
// VPN with no underlying networks should be metered by default.
assertTrue(isNetworkMetered(mNetwork));
assertTrue(mCM.isActiveNetworkMetered());
+
+ expectVpnTransportInfo(mCM.getActiveNetwork());
}
public void testVpnMeterednessWithNullUnderlyingNetwork() throws Exception {
@@ -944,6 +1015,8 @@
assertEquals(isNetworkMetered(underlyingNetwork), isNetworkMetered(mNetwork));
// Meteredness based on VPN capabilities and CM#isActiveNetworkMetered should be in sync.
assertEquals(isNetworkMetered(mNetwork), mCM.isActiveNetworkMetered());
+
+ expectVpnTransportInfo(mCM.getActiveNetwork());
}
public void testVpnMeterednessWithNonNullUnderlyingNetwork() throws Exception {
@@ -971,6 +1044,8 @@
assertEquals(isNetworkMetered(underlyingNetwork), isNetworkMetered(mNetwork));
// Meteredness based on VPN capabilities and CM#isActiveNetworkMetered should be in sync.
assertEquals(isNetworkMetered(mNetwork), mCM.isActiveNetworkMetered());
+
+ expectVpnTransportInfo(mCM.getActiveNetwork());
}
public void testAlwaysMeteredVpnWithNullUnderlyingNetwork() throws Exception {
@@ -995,6 +1070,8 @@
// VPN's meteredness does not depend on underlying network since it is always metered.
assertTrue(isNetworkMetered(mNetwork));
assertTrue(mCM.isActiveNetworkMetered());
+
+ expectVpnTransportInfo(mCM.getActiveNetwork());
}
public void testAlwaysMeteredVpnWithNonNullUnderlyingNetwork() throws Exception {
@@ -1020,6 +1097,8 @@
// VPN's meteredness does not depend on underlying network since it is always metered.
assertTrue(isNetworkMetered(mNetwork));
assertTrue(mCM.isActiveNetworkMetered());
+
+ expectVpnTransportInfo(mCM.getActiveNetwork());
}
public void testB141603906() throws Exception {
@@ -1069,6 +1148,14 @@
}
}
+ private void expectVpnTransportInfo(Network network) {
+ final NetworkCapabilities vpnNc = mCM.getNetworkCapabilities(network);
+ assertTrue(vpnNc.hasTransport(TRANSPORT_VPN));
+ final TransportInfo ti = vpnNc.getTransportInfo();
+ assertTrue(ti instanceof VpnTransportInfo);
+ assertEquals(VpnManager.TYPE_VPN_SERVICE, ((VpnTransportInfo) ti).type);
+ }
+
private void assertDefaultProxy(ProxyInfo expected) {
assertEquals("Incorrect proxy config.", expected, mCM.getDefaultProxy());
String expectedHost = expected == null ? null : expected.getHost();
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
index 590e17e..1c9ff05 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
@@ -138,7 +138,7 @@
}
}
};
- mCm.registerNetworkCallback(makeWifiNetworkRequest(), mNetworkCallback);
+ mCm.registerNetworkCallback(makeNetworkRequest(), mNetworkCallback);
try {
cb.asBinder().linkToDeath(() -> unregisterNetworkCallback(), 0);
} catch (RemoteException e) {
@@ -156,9 +156,8 @@
}
};
- private NetworkRequest makeWifiNetworkRequest() {
+ private NetworkRequest makeNetworkRequest() {
return new NetworkRequest.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build();
}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index cbf43e7..831810c 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -25,10 +25,12 @@
import static android.content.pm.PackageManager.FEATURE_WIFI;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.cts.util.CtsNetUtils.ConnectivityActionReceiver;
import static android.net.cts.util.CtsNetUtils.HTTP_PORT;
@@ -43,6 +45,7 @@
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.testutils.MiscAsserts.assertThrows;
import static com.android.testutils.TestPermissionUtil.runAsShell;
import static org.junit.Assert.assertEquals;
@@ -68,8 +71,10 @@
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
+import android.net.InetAddresses;
import android.net.IpSecManager;
import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -80,11 +85,15 @@
import android.net.NetworkRequest;
import android.net.NetworkUtils;
import android.net.SocketKeepalive;
+import android.net.StringNetworkSpecifier;
+import android.net.TestNetworkInterface;
+import android.net.TestNetworkManager;
import android.net.cts.util.CtsNetUtils;
import android.net.util.KeepaliveUtils;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Build;
+import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
import android.os.SystemClock;
@@ -104,10 +113,10 @@
import com.android.testutils.SkipPresubmit;
import com.android.testutils.TestableNetworkCallback;
-import libcore.io.Streams;
-
import junit.framework.AssertionFailedError;
+import libcore.io.Streams;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -176,6 +185,9 @@
private static final String KEEPALIVE_RESERVED_PER_SLOT_RES_NAME =
"config_reservedPrivilegedKeepaliveSlots";
+ private static final LinkAddress TEST_LINKADDR = new LinkAddress(
+ InetAddresses.parseNumericAddress("2001:db8::8"), 64);
+
private Context mContext;
private Instrumentation mInstrumentation;
private ConnectivityManager mCm;
@@ -183,7 +195,6 @@
private PackageManager mPackageManager;
private final HashMap<Integer, NetworkConfig> mNetworks =
new HashMap<Integer, NetworkConfig>();
- boolean mWifiWasDisabled;
private UiAutomation mUiAutomation;
private CtsNetUtils mCtsNetUtils;
@@ -195,7 +206,6 @@
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
mPackageManager = mContext.getPackageManager();
mCtsNetUtils = new CtsNetUtils(mContext);
- mWifiWasDisabled = false;
// Get com.android.internal.R.array.networkAttributes
int resId = mContext.getResources().getIdentifier("networkAttributes", "array", "android");
@@ -218,10 +228,7 @@
@After
public void tearDown() throws Exception {
- // Return WiFi to its original disabled state after tests that explicitly connect.
- if (mWifiWasDisabled) {
- mCtsNetUtils.disconnectFromWifi(null);
- }
+ // Release any NetworkRequests filed to connect mobile data.
if (mCtsNetUtils.cellConnectAttempted()) {
mCtsNetUtils.disconnectFromCell();
}
@@ -237,17 +244,6 @@
}
}
- /**
- * Make sure WiFi is connected to an access point if it is not already. If
- * WiFi is enabled as a result of this function, it will be disabled
- * automatically in tearDown().
- */
- private Network ensureWifiConnected() {
- mWifiWasDisabled = !mWifiManager.isWifiEnabled();
- // Even if wifi is enabled, the network may not be connected or ready yet
- return mCtsNetUtils.connectToWifi();
- }
-
@Test
public void testIsNetworkTypeValid() {
assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE));
@@ -521,25 +517,38 @@
final TestNetworkCallback defaultTrackingCallback = new TestNetworkCallback();
mCm.registerDefaultNetworkCallback(defaultTrackingCallback);
+ final TestNetworkCallback systemDefaultTrackingCallback = new TestNetworkCallback();
+ runWithShellPermissionIdentity(() ->
+ mCm.registerSystemDefaultNetworkCallback(systemDefaultTrackingCallback,
+ new Handler(Looper.getMainLooper())),
+ NETWORK_SETTINGS);
+
+
Network wifiNetwork = null;
try {
- ensureWifiConnected();
+ mCtsNetUtils.ensureWifiConnected();
// Now we should expect to get a network callback about availability of the wifi
// network even if it was already connected as a state-based action when the callback
// is registered.
wifiNetwork = callback.waitForAvailable();
- assertNotNull("Did not receive NetworkCallback.onAvailable for TRANSPORT_WIFI",
+ assertNotNull("Did not receive onAvailable for TRANSPORT_WIFI request",
wifiNetwork);
- assertNotNull("Did not receive NetworkCallback.onAvailable for any default network",
+ assertNotNull("Did not receive onAvailable on default network callback",
defaultTrackingCallback.waitForAvailable());
+
+ assertNotNull("Did not receive onAvailable on system default network callback",
+ systemDefaultTrackingCallback.waitForAvailable());
} catch (InterruptedException e) {
fail("Broadcast receiver or NetworkCallback wait was interrupted.");
} finally {
mCm.unregisterNetworkCallback(callback);
mCm.unregisterNetworkCallback(defaultTrackingCallback);
+ runWithShellPermissionIdentity(
+ () -> mCm.unregisterNetworkCallback(systemDefaultTrackingCallback),
+ NETWORK_SETTINGS);
}
}
@@ -574,7 +583,7 @@
mCm.registerNetworkCallback(makeWifiNetworkRequest(), pendingIntent);
try {
- ensureWifiConnected();
+ mCtsNetUtils.ensureWifiConnected();
// Now we expect to get the Intent delivered notifying of the availability of the wifi
// network even if it was already connected as a state-based action when the callback
@@ -655,6 +664,17 @@
return null;
}
+ /**
+ * Checks that enabling/disabling wifi causes CONNECTIVITY_ACTION broadcasts.
+ */
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test
+ public void testToggleWifiConnectivityAction() {
+ // toggleWifi calls connectToWifi and disconnectFromWifi, which both wait for
+ // CONNECTIVITY_ACTION broadcasts.
+ mCtsNetUtils.toggleWifi();
+ }
+
/** Verify restricted networks cannot be requested. */
@AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
@Test
@@ -782,7 +802,7 @@
@Test
public void testGetMultipathPreference() throws Exception {
final ContentResolver resolver = mContext.getContentResolver();
- ensureWifiConnected();
+ mCtsNetUtils.ensureWifiConnected();
final String ssid = unquoteSSID(mWifiManager.getConnectionInfo().getSSID());
final String oldMeteredSetting = getWifiMeteredStatus(ssid);
final String oldMeteredMultipathPreference = Settings.Global.getString(
@@ -796,7 +816,7 @@
waitForActiveNetworkMetered(TRANSPORT_WIFI, true);
// Wifi meterness changes from unmetered to metered will disconnect and reconnect since
// R.
- final Network network = ensureWifiConnected();
+ final Network network = mCtsNetUtils.ensureWifiConnected();
assertEquals(ssid, unquoteSSID(mWifiManager.getConnectionInfo().getSSID()));
assertEquals(mCm.getNetworkCapabilities(network).hasCapability(
NET_CAPABILITY_NOT_METERED), false);
@@ -1010,7 +1030,7 @@
return;
}
- final Network network = ensureWifiConnected();
+ final Network network = mCtsNetUtils.ensureWifiConnected();
if (getSupportedKeepalivesForNet(network) != 0) return;
final InetAddress srcAddr = getFirstV4Address(network);
assumeTrue("This test requires native IPv4", srcAddr != null);
@@ -1030,7 +1050,7 @@
return;
}
- final Network network = ensureWifiConnected();
+ final Network network = mCtsNetUtils.ensureWifiConnected();
if (getSupportedKeepalivesForNet(network) == 0) return;
final InetAddress srcAddr = getFirstV4Address(network);
assumeTrue("This test requires native IPv4", srcAddr != null);
@@ -1241,7 +1261,7 @@
return;
}
- final Network network = ensureWifiConnected();
+ final Network network = mCtsNetUtils.ensureWifiConnected();
final int supported = getSupportedKeepalivesForNet(network);
if (supported == 0) {
return;
@@ -1338,7 +1358,7 @@
return;
}
- final Network network = ensureWifiConnected();
+ final Network network = mCtsNetUtils.ensureWifiConnected();
final int supported = getSupportedKeepalivesForNet(network);
if (supported == 0) {
return;
@@ -1386,7 +1406,7 @@
// Ensure that NetworkUtils.queryUserAccess always returns false since this package should
// not have netd system permission to call this function.
- final Network wifiNetwork = ensureWifiConnected();
+ final Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
assertFalse(NetworkUtils.queryUserAccess(Binder.getCallingUid(), wifiNetwork.netId));
// Ensure that this package cannot bind to any restricted network that's currently
@@ -1531,4 +1551,72 @@
throw new AssertionFailedError("Captive portal server URL is invalid: " + e);
}
}
+
+ /**
+ * Verify background request can only be requested when acquiring
+ * {@link android.Manifest.permission.NETWORK_SETTINGS}.
+ */
+ @SkipPresubmit(reason = "Flaky: b/179554972; add to presubmit after fixing")
+ @Test
+ public void testRequestBackgroundNetwork() throws Exception {
+ // Create a tun interface. Use the returned interface name as the specifier to create
+ // a test network request.
+ final TestNetworkInterface testNetworkInterface = runWithShellPermissionIdentity(() -> {
+ final TestNetworkManager tnm =
+ mContext.getSystemService(TestNetworkManager.class);
+ return tnm.createTunInterface(new LinkAddress[]{TEST_LINKADDR});
+ }, android.Manifest.permission.MANAGE_TEST_NETWORKS,
+ android.Manifest.permission.NETWORK_SETTINGS);
+ assertNotNull(testNetworkInterface);
+
+ final NetworkRequest testRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_TEST)
+ // Test networks do not have NOT_VPN or TRUSTED capabilities by default
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+ .setNetworkSpecifier(
+ new StringNetworkSpecifier(testNetworkInterface.getInterfaceName()))
+ .build();
+
+ // Verify background network cannot be requested without NETWORK_SETTINGS permission.
+ final TestableNetworkCallback callback = new TestableNetworkCallback();
+ assertThrows(SecurityException.class,
+ () -> mCm.requestBackgroundNetwork(testRequest, null, callback));
+
+ try {
+ // Request background test network via Shell identity which has NETWORK_SETTINGS
+ // permission granted.
+ runWithShellPermissionIdentity(
+ () -> mCm.requestBackgroundNetwork(testRequest, null, callback),
+ android.Manifest.permission.NETWORK_SETTINGS);
+
+ // Register the test network agent which has no foreground request associated to it.
+ // And verify it can satisfy the background network request just fired.
+ final Binder binder = new Binder();
+ runWithShellPermissionIdentity(() -> {
+ final TestNetworkManager tnm =
+ mContext.getSystemService(TestNetworkManager.class);
+ tnm.setupTestNetwork(testNetworkInterface.getInterfaceName(), binder);
+ }, android.Manifest.permission.MANAGE_TEST_NETWORKS,
+ android.Manifest.permission.NETWORK_SETTINGS);
+ waitForAvailable(callback);
+ final Network testNetwork = callback.getLastAvailableNetwork();
+ assertNotNull(testNetwork);
+
+ // The test network that has just connected is a foreground network,
+ // non-listen requests will get available callback before it can be put into
+ // background if no foreground request can be satisfied. Thus, wait for a short
+ // period is needed to let foreground capability go away.
+ callback.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
+ callback.getDefaultTimeoutMs(),
+ c -> c instanceof CallbackEntry.CapabilitiesChanged
+ && !((CallbackEntry.CapabilitiesChanged) c).getCaps()
+ .hasCapability(NET_CAPABILITY_FOREGROUND));
+ final NetworkCapabilities nc = mCm.getNetworkCapabilities(testNetwork);
+ assertFalse("expected background network, but got " + nc,
+ nc.hasCapability(NET_CAPABILITY_FOREGROUND));
+ } finally {
+ mCm.unregisterNetworkCallback(callback);
+ }
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
index 9eab024..8f2d93d 100644
--- a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
+++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
@@ -55,7 +55,7 @@
import androidx.test.InstrumentationRegistry;
import com.android.internal.util.HexDump;
-import com.android.org.bouncycastle.x509.X509V1CertificateGenerator;
+import com.android.internal.org.bouncycastle.x509.X509V1CertificateGenerator;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index a429faa..aea33ca 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -47,6 +47,8 @@
import android.net.SocketKeepalive
import android.net.StringNetworkSpecifier
import android.net.Uri
+import android.net.VpnManager
+import android.net.VpnTransportInfo
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnAddKeepalivePacketFilter
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnAutomaticReconnectDisabled
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnBandwidthUpdateRequested
@@ -541,7 +543,7 @@
@Test
@IgnoreUpTo(Build.VERSION_CODES.R)
- fun testSetUnderlyingNetworks() {
+ fun testSetUnderlyingNetworksAndVpnSpecifier() {
val request = NetworkRequest.Builder()
.addTransportType(TRANSPORT_TEST)
.addTransportType(TRANSPORT_VPN)
@@ -555,6 +557,7 @@
addTransportType(TRANSPORT_TEST)
addTransportType(TRANSPORT_VPN)
removeCapability(NET_CAPABILITY_NOT_VPN)
+ setTransportInfo(VpnTransportInfo(VpnManager.TYPE_VPN_SERVICE))
}
val defaultNetwork = mCM.activeNetwork
assertNotNull(defaultNetwork)
@@ -569,6 +572,8 @@
// Check that the default network's transport is propagated to the VPN.
var vpnNc = mCM.getNetworkCapabilities(agent.network)
assertNotNull(vpnNc)
+ assertEquals(VpnManager.TYPE_VPN_SERVICE,
+ (vpnNc.transportInfo as VpnTransportInfo).type)
val testAndVpn = intArrayOf(TRANSPORT_TEST, TRANSPORT_VPN)
assertTrue(hasAllTransports(vpnNc, testAndVpn))
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index 0527011..88172d7 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -212,7 +212,7 @@
mContext.registerReceiver(receiver, filter);
boolean connected = false;
- final String err = "Wifi must be configured to connect to an access point for this test.";
+ final String err = "Wifi must be configured to connect to an access point for this test";
try {
clearWifiBlacklist();
SystemUtil.runShellCommand("svc wifi enable");
@@ -235,7 +235,7 @@
}
// Ensure we get an onAvailable callback and possibly a CONNECTIVITY_ACTION.
wifiNetwork = callback.waitForAvailable();
- assertNotNull(err, wifiNetwork);
+ assertNotNull(err + ": onAvailable callback not received", wifiNetwork);
connected = !expectLegacyBroadcast || receiver.waitForState();
} catch (InterruptedException ex) {
fail("connectToWifi was interrupted");
@@ -244,7 +244,7 @@
mContext.unregisterReceiver(receiver);
}
- assertTrue(err, connected);
+ assertTrue(err + ": CONNECTIVITY_ACTION not received", connected);
return wifiNetwork;
}