Merge "Move connectivity-sources to frameworks/base"
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 c0d85ae..2bdddc1 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.Tether4Key;
+import com.android.networkstack.tethering.Tether4Value;
+import com.android.networkstack.tethering.Tether6Value;
import com.android.networkstack.tethering.TetherDownstream6Key;
-import com.android.networkstack.tethering.TetherDownstream6Value;
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<TetherDownstream6Key, TetherDownstream6Value> mBpfDownstream6Map;
+ 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,14 +84,18 @@
public BpfCoordinatorShimImpl(@NonNull final Dependencies deps) {
mLog = deps.getSharedLog().forSubComponent(TAG);
+ mBpfDownstream4Map = deps.getBpfDownstream4Map();
+ mBpfUpstream4Map = deps.getBpfUpstream4Map();
mBpfDownstream6Map = deps.getBpfDownstream6Map();
+ mBpfUpstream6Map = deps.getBpfUpstream6Map();
mBpfStatsMap = deps.getBpfStatsMap();
mBpfLimitMap = deps.getBpfLimitMap();
}
@Override
public boolean isInitialized() {
- return mBpfDownstream6Map != null && mBpfStatsMap != null && mBpfLimitMap != null;
+ return mBpfDownstream4Map != null && mBpfUpstream4Map != null && mBpfDownstream6Map != null
+ && mBpfUpstream6Map != null && mBpfStatsMap != null && mBpfLimitMap != null;
}
@Override
@@ -84,7 +103,7 @@
if (!isInitialized()) return false;
final TetherDownstream6Key key = rule.makeTetherDownstream6Key();
- final TetherDownstream6Value value = rule.makeTetherDownstream6Value();
+ final Tether6Value value = rule.makeTether6Value();
try {
mBpfDownstream6Map.updateEntry(key, value);
@@ -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;
+ }
+
+ @Override
public String toString() {
- return "mBpfDownstream6Map{"
+ return "mBpfDownstream4Map{"
+ + (mBpfDownstream4Map != null ? "initialized" : "not initialized") + "}, "
+ + "mBpfUpstream4Map{"
+ + (mBpfUpstream4Map != null ? "initialized" : "not initialized") + "}, "
+ + "mBpfUpstream6Map{"
+ + (mBpfUpstream6Map != null ? "initialized" : "not initialized") + "}, "
+ + "mBpfDownstream6Map{"
+ (mBpfDownstream6Map != null ? "initialized" : "not initialized") + "}, "
+ "mBpfStatsMap{"
+ (mBpfStatsMap != null ? "initialized" : "not initialized") + "}, "
+ "mBpfLimitMap{"
- + (mBpfLimitMap != null ? "initialized" : "not initialized") + "} "
- + "}";
+ + (mBpfLimitMap != null ? "initialized" : "not initialized") + "} ";
}
/**
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/offload.c b/Tethering/bpf_progs/offload.c
index 852de1e..3a3291d 100644
--- a/Tethering/bpf_progs/offload.c
+++ b/Tethering/bpf_progs/offload.c
@@ -20,10 +20,17 @@
#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 "netdbpf/bpf_shared.h"
+// From kernel:include/net/ip.h
+#define IP_DF 0x4000 // Flag: "Don't Fragment"
+
// Tethering stats, indexed by upstream interface.
DEFINE_BPF_MAP_GRW(tether_stats_map, HASH, TetherStatsKey, TetherStatsValue, 16, AID_NETWORK_STACK)
@@ -42,7 +49,7 @@
DEFINE_BPF_MAP_GRW(tether_upstream6_map, HASH, TetherUpstream6Key, TetherUpstream6Value, 64,
AID_NETWORK_STACK)
-static inline __always_inline int do_forward(struct __sk_buff* skb, const bool is_ethernet,
+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;
@@ -50,6 +57,9 @@
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;
@@ -207,13 +217,13 @@
DEFINE_BPF_PROG("schedcls/tether_downstream6_ether", AID_ROOT, AID_NETWORK_STACK,
sched_cls_tether_downstream6_ether)
(struct __sk_buff* skb) {
- return do_forward(skb, /* is_ethernet */ true, /* downstream */ true);
+ 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_forward(skb, /* is_ethernet */ true, /* downstream */ false);
+ return do_forward6(skb, /* is_ethernet */ true, /* downstream */ false);
}
// Note: section names must be unique to prevent programs from appending to each other,
@@ -232,13 +242,13 @@
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, /* is_ethernet */ false, /* downstream */ true);
+ return do_forward6(skb, /* is_ethernet */ false, /* downstream */ true);
}
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, /* is_ethernet */ false, /* downstream */ false);
+ return do_forward6(skb, /* is_ethernet */ false, /* downstream */ false);
}
// and these identical optional (may fail to load) implementations for [4.14..5.4) patched kernels:
@@ -247,7 +257,7 @@
sched_cls_tether_downstream6_rawip_4_14,
KVER(4, 14, 0), KVER(5, 4, 0))
(struct __sk_buff* skb) {
- return do_forward(skb, /* is_ethernet */ false, /* downstream */ true);
+ return do_forward6(skb, /* is_ethernet */ false, /* downstream */ true);
}
DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream6_rawip$4_14",
@@ -255,7 +265,7 @@
sched_cls_tether_upstream6_rawip_4_14,
KVER(4, 14, 0), KVER(5, 4, 0))
(struct __sk_buff* skb) {
- return do_forward(skb, /* is_ethernet */ false, /* downstream */ false);
+ 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.
@@ -275,32 +285,189 @@
// ----- IPv4 Support -----
-DEFINE_BPF_MAP_GRW(tether_downstream4_map, HASH, TetherDownstream4Key, TetherDownstream4Value, 64,
- AID_NETWORK_STACK)
+DEFINE_BPF_MAP_GRW(tether_downstream4_map, HASH, Tether4Key, Tether4Value, 64, AID_NETWORK_STACK)
-DEFINE_BPF_MAP_GRW(tether_upstream4_map, HASH, TetherUpstream4Key, TetherUpstream4Value, 64,
- AID_NETWORK_STACK)
+DEFINE_BPF_MAP_GRW(tether_upstream4_map, HASH, Tether4Key, Tether4Value, 64, AID_NETWORK_STACK)
-DEFINE_BPF_PROG("schedcls/tether_downstream4_ether", AID_ROOT, AID_NETWORK_STACK,
- sched_cls_tether_downstream4_ether)
+static inline __always_inline int do_forward4(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 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) return TC_ACT_OK;
+
+ // We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header
+ if (ip->ihl != 5) return TC_ACT_OK;
+
+ // 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) return TC_ACT_OK;
+
+ // Minimum IPv4 total length is the size of the header
+ if (ntohs(ip->tot_len) < sizeof(*ip)) return TC_ACT_OK;
+
+ // We are incapable of dealing with IPv4 fragments
+ if (ip->frag_off & ~htons(IP_DF)) return TC_ACT_OK;
+
+ // 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) return TC_ACT_OK;
+
+ const bool is_tcp = (ip->protocol == IPPROTO_TCP);
+
+ // We do not support anything besides TCP and UDP
+ if (!is_tcp && (ip->protocol != IPPROTO_UDP)) return TC_ACT_OK;
+
+ 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) return TC_ACT_OK;
+
+ // 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) return TC_ACT_OK;
+ } else { // UDP
+ // Make sure we can get at the udp header
+ if (data + l2_header_size + sizeof(*ip) + sizeof(*udph) > data_end) return TC_ACT_OK;
+ }
+
+ 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) return TC_ACT_OK;
+
+ 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;
+
+ // Required IPv4 minimum mtu is 68, below that not clear what we should do, abort...
+ if (v->pmtu < 68) return TC_ACT_OK;
+
+ // 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) return TC_ACT_OK;
+
+ // TODO: replace Errors with Packets once implemented
+ __sync_fetch_and_add(downstream ? &stat_v->rxErrors : &stat_v->txErrors, packets);
+ __sync_fetch_and_add(downstream ? &stat_v->rxBytes : &stat_v->txBytes, bytes);
+
+ // TODO: not actually implemented yet
+ return TC_ACT_OK;
+}
+
+// Real implementations for 5.9+ kernels
+
+DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_ether$5_9", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_downstream4_ether_5_9, KVER(5, 9, 0))
+(struct __sk_buff* skb) {
+ return do_forward4(skb, /* is_ethernet */ true, /* downstream */ true);
+}
+
+DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_rawip$5_9", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_downstream4_rawip_5_9, KVER(5, 9, 0))
+(struct __sk_buff* skb) {
+ return do_forward4(skb, /* is_ethernet */ false, /* downstream */ true);
+}
+
+DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_ether$5_9", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_upstream4_ether_5_9, KVER(5, 9, 0))
+(struct __sk_buff* skb) {
+ return do_forward4(skb, /* is_ethernet */ true, /* downstream */ false);
+}
+
+DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_rawip$5_9", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_upstream4_rawip_5_9, KVER(5, 9, 0))
+(struct __sk_buff* skb) {
+ return do_forward4(skb, /* is_ethernet */ false, /* downstream */ false);
+}
+
+// Placeholder implementations for older pre-5.9 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(5, 9, 0))
(struct __sk_buff* skb) {
return TC_ACT_OK;
}
-DEFINE_BPF_PROG("schedcls/tether_downstream4_rawip", AID_ROOT, AID_NETWORK_STACK,
- sched_cls_tether_downstream4_rawip)
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$stub", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_downstream4_rawip_stub, KVER_NONE, KVER(5, 9, 0))
(struct __sk_buff* skb) {
return TC_ACT_OK;
}
-DEFINE_BPF_PROG("schedcls/tether_upstream4_ether", AID_ROOT, AID_NETWORK_STACK,
- sched_cls_tether_upstream4_ether)
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$stub", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_upstream4_ether_stub, KVER_NONE, KVER(5, 9, 0))
(struct __sk_buff* skb) {
return TC_ACT_OK;
}
-DEFINE_BPF_PROG("schedcls/tether_upstream4_rawip", AID_ROOT, AID_NETWORK_STACK,
- sched_cls_tether_upstream4_rawip)
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$stub", AID_ROOT, AID_NETWORK_STACK,
+ sched_cls_tether_upstream4_rawip_stub, KVER_NONE, KVER(5, 9, 0))
(struct __sk_buff* skb) {
return TC_ACT_OK;
}
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 7f3e80f..194737a 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -71,10 +71,8 @@
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;
@@ -187,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);
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 3268e94..35de400 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -23,7 +23,9 @@
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;
@@ -38,6 +40,7 @@
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;
@@ -80,14 +83,28 @@
* @hide
*/
public class BpfCoordinator {
+ 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_DOWNSTREAM6_FS_PATH =
- "/sys/fs/bpf/tethering/map_offload_tether_downstream6_map";
- private static final String TETHER_STATS_MAP_PATH =
- "/sys/fs/bpf/tethering/map_offload_tether_stats_map";
- private static final String TETHER_LIMIT_MAP_PATH =
- "/sys/fs/bpf/tethering/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 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 {
@@ -174,6 +191,9 @@
// - 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<>();
@@ -224,18 +244,50 @@
return SdkLevel.isAtLeastS();
}
+ /** Get downstream4 BPF map. */
+ @Nullable public BpfMap<Tether4Key, Tether4Value> getBpfDownstream4Map() {
+ try {
+ return new BpfMap<>(TETHER_DOWNSTREAM4_MAP_PATH,
+ BpfMap.BPF_F_RDWR, Tether4Key.class, Tether4Value.class);
+ } catch (ErrnoException 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, TetherDownstream6Value>
- getBpfDownstream6Map() {
+ @Nullable public BpfMap<TetherDownstream6Key, Tether6Value> getBpfDownstream6Map() {
try {
return new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH,
- BpfMap.BPF_F_RDWR, TetherDownstream6Key.class, TetherDownstream6Value.class);
+ 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;
+ }
+ }
+
/** Get stats BPF map. */
@Nullable public BpfMap<TetherStatsKey, TetherStatsValue> getBpfStatsMap() {
try {
@@ -400,7 +452,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
@@ -411,6 +463,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
@@ -443,6 +508,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)) {
@@ -491,12 +566,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));
}
}
@@ -719,11 +804,11 @@
}
/**
- * Return a TetherDownstream6Value object built from the rule.
+ * Return a Tether6Value object built from the rule.
*/
@NonNull
- public TetherDownstream6Value makeTetherDownstream6Value() {
- return new TetherDownstream6Value(downstreamIfindex, dstMac, srcMac, ETH_P_IPV6,
+ public Tether6Value makeTether6Value() {
+ return new Tether6Value(downstreamIfindex, dstMac, srcMac, ETH_P_IPV6,
NetworkStackConstants.ETHER_MTU);
}
@@ -854,8 +939,97 @@
}
}
+ @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 {
- public void accept(ConntrackMonitor.ConntrackEvent e) { /* TODO */ }
+ @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() {
@@ -926,6 +1100,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) {
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfMap.java b/Tethering/src/com/android/networkstack/tethering/BpfMap.java
index 78d212c..89caa8a 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfMap.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfMap.java
@@ -134,6 +134,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());
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/TetherDownstream6Value.java b/Tethering/src/com/android/networkstack/tethering/Tether6Value.java
similarity index 66%
rename from Tethering/src/com/android/networkstack/tethering/TetherDownstream6Value.java
rename to Tethering/src/com/android/networkstack/tethering/Tether6Value.java
index a56269d..b3107fd 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetherDownstream6Value.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 TetherDownstream6Value 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 TetherDownstream6Value(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 TetherDownstream6Value)) return false;
-
- final TetherDownstream6Value that = (TetherDownstream6Value) 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/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/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
index 5520f30..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;
@@ -54,7 +53,9 @@
private static final String TETHER_DOWNSTREAM6_FS_PATH =
"/sys/fs/bpf/tethering/map_test_tether_downstream6_map";
- private ArrayMap<TetherDownstream6Key, TetherDownstream6Value> mTestData;
+ private ArrayMap<TetherDownstream6Key, Tether6Value> mTestData;
+
+ private BpfMap<TetherDownstream6Key, Tether6Value> mTestMap;
@BeforeClass
public static void setupOnce() {
@@ -63,46 +64,34 @@
@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(createTetherDownstream6Key(101, "2001:db8::1"),
- createTetherDownstream6Value(11, "00:00:00:00:00:0a", "11:11:11:00:00:0b",
+ 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"),
- createTetherDownstream6Value(22, "00:00:00:00:00:0c", "22:22:22:00:00:0d",
+ 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"),
- createTetherDownstream6Value(33, "00:00:00:00:00:0e", "33:33:33:00:00:0f",
+ 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 BpfMap<TetherDownstream6Key, TetherDownstream6Value> getTestMap() throws Exception {
- return new BpfMap<>(
+ private void initTestMap() throws Exception {
+ mTestMap = new BpfMap<>(
TETHER_DOWNSTREAM6_FS_PATH, BpfMap.BPF_F_RDWR,
- TetherDownstream6Key.class, TetherDownstream6Value.class);
- }
+ TetherDownstream6Key.class, Tether6Value.class);
- private void cleanTestMap() throws Exception {
- try (BpfMap<TetherDownstream6Key, TetherDownstream6Value> 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());
- }
+ 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 TetherDownstream6Key createTetherDownstream6Key(long iif, String address)
@@ -112,18 +101,17 @@
return new TetherDownstream6Key(iif, ipv6Address.getAddress());
}
- private TetherDownstream6Value createTetherDownstream6Value(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 TetherDownstream6Value(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_DOWNSTREAM6_FS_PATH, BpfMap.BPF_F_RDONLY,
- TetherDownstream6Key.class, TetherDownstream6Value.class)) {
+ TetherDownstream6Key.class, Tether6Value.class)) {
assertNotNull(readOnlyMap);
try {
readOnlyMap.insertEntry(mTestData.keyAt(0), mTestData.valueAt(0));
@@ -133,7 +121,7 @@
}
}
try (BpfMap writeOnlyMap = new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH, BpfMap.BPF_F_WRONLY,
- TetherDownstream6Key.class, TetherDownstream6Value.class)) {
+ TetherDownstream6Key.class, Tether6Value.class)) {
assertNotNull(writeOnlyMap);
try {
writeOnlyMap.getFirstKey();
@@ -143,217 +131,211 @@
}
}
try (BpfMap readWriteMap = new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH, BpfMap.BPF_F_RDWR,
- TetherDownstream6Key.class, TetherDownstream6Value.class)) {
+ TetherDownstream6Key.class, Tether6Value.class)) {
assertNotNull(readWriteMap);
}
}
@Test
- public void testGetFirstKey() throws Exception {
- try (BpfMap<TetherDownstream6Key, TetherDownstream6Value> 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<TetherDownstream6Key, TetherDownstream6Value> bpfMap = getTestMap()) {
- // [1] If the passed-in key is not found on empty map, return null.
- final TetherDownstream6Key nonexistentKey =
- createTetherDownstream6Key(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<TetherDownstream6Key, TetherDownstream6Value> 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 TetherDownstream6Key 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.
- TetherDownstream6Key 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<TetherDownstream6Key, TetherDownstream6Value> 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 TetherDownstream6Key key = mTestData.keyAt(0);
- final TetherDownstream6Value value = mTestData.valueAt(0);
- final TetherDownstream6Value 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 TetherDownstream6Value 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 TetherDownstream6Value 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<TetherDownstream6Key, TetherDownstream6Value> bpfMap = getTestMap()) {
+ final TetherDownstream6Key key = mTestData.keyAt(0);
+ final Tether6Value value = mTestData.valueAt(0);
+ final Tether6Value value2 = mTestData.valueAt(1);
- final TetherDownstream6Key key = mTestData.keyAt(0);
- final TetherDownstream6Value value = mTestData.valueAt(0);
- final TetherDownstream6Value 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 TetherDownstream6Value 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 TetherDownstream6Value 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<TetherDownstream6Key, TetherDownstream6Value> bpfMap = getTestMap()) {
- final ArrayMap<TetherDownstream6Key, TetherDownstream6Value> 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<TetherDownstream6Key, TetherDownstream6Value> 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<TetherDownstream6Key, TetherDownstream6Value> bpfMap = getTestMap()) {
- final ArrayMap<TetherDownstream6Key, TetherDownstream6Value> 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<TetherDownstream6Key, TetherDownstream6Value> bpfMap = getTestMap()) {
- final ArrayMap<TetherDownstream6Key, TetherDownstream6Value> 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(createTetherDownstream6Key(i, "2001:db8::1"),
- createTetherDownstream6Value(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/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index e20e011..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.Tether4Key;
+import com.android.networkstack.tethering.Tether4Value;
+import com.android.networkstack.tethering.Tether6Value;
import com.android.networkstack.tethering.TetherDownstream6Key;
-import com.android.networkstack.tethering.TetherDownstream6Value;
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;
@@ -173,7 +176,10 @@
@Mock private NetworkStatsManager mStatsManager;
@Mock private TetheringConfiguration mTetherConfig;
@Mock private ConntrackMonitor mConntrackMonitor;
- @Mock private BpfMap<TetherDownstream6Key, TetherDownstream6Value> mBpfDownstream6Map;
+ @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;
@@ -200,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) {
@@ -303,12 +306,26 @@
}
@Nullable
- public BpfMap<TetherDownstream6Key, TetherDownstream6Value>
- getBpfDownstream6Map() {
+ 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
public BpfMap<TetherStatsKey, TetherStatsValue> getBpfStatsMap() {
return mBpfStatsMap;
}
@@ -784,8 +801,8 @@
}
@NonNull
- private static TetherDownstream6Value makeDownstream6Value(@NonNull final MacAddress dstMac) {
- return new TetherDownstream6Value(TEST_IFACE_PARAMS.index, dstMac,
+ 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);
}
@@ -849,6 +866,36 @@
}
}
+ 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();
@@ -857,12 +904,19 @@
}
private void resetNetdBpfMapAndCoordinator() throws Exception {
- reset(mNetd, mBpfDownstream6Map, 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
@@ -873,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");
@@ -883,33 +936,35 @@
final MacAddress macB = MacAddress.fromString("11:22:33:00:00:0b");
resetNetdBpfMapAndCoordinator();
- verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map);
+ 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, mBpfDownstream6Map);
+ 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, mBpfDownstream6Map);
+ verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map, mBpfUpstream6Map);
recvNewNeigh(myIfindex, neighMC, NUD_REACHABLE, macA);
- verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map);
+ 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.
@@ -917,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.
@@ -924,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, mBpfDownstream6Map);
+ 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.
@@ -951,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.
@@ -959,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, mBpfDownstream6Map);
+ verifyNoUpstreamIpv6ForwardingChange(null);
+ verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map, mBpfUpstream6Map);
// Rules can be added again once upstream IPv6 connectivity is available.
lp.setInterfaceName(UPSTREAM_IFACE);
@@ -968,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);
@@ -977,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);
@@ -986,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);
@@ -996,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();
}
@@ -1024,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.
@@ -1041,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 764e651..1270e50 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -158,7 +158,10 @@
@Mock private IpServer mIpServer2;
@Mock private TetheringConfiguration mTetherConfig;
@Mock private ConntrackMonitor mConntrackMonitor;
- @Mock private BpfMap<TetherDownstream6Key, TetherDownstream6Value> mBpfDownstream6Map;
+ @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;
@@ -203,12 +206,26 @@
}
@Nullable
- public BpfMap<TetherDownstream6Key, TetherDownstream6Value>
- getBpfDownstream6Map() {
+ 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
public BpfMap<TetherStatsKey, TetherStatsValue> getBpfStatsMap() {
return mBpfStatsMap;
}
@@ -348,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, mBpfDownstream6Map).updateEntry(
- rule.makeTetherDownstream6Key(), rule.makeTetherDownstream6Value());
+ rule.makeTetherDownstream6Key(), rule.makeTether6Value());
} else {
verifyWithOrder(inOrder, mNetd).tetherOffloadRuleAdd(matches(rule));
}
@@ -674,11 +721,11 @@
}
@Test
- public void testRuleMakeTetherDownstream6Value() throws Exception {
+ public void testRuleMakeTether6Value() throws Exception {
final Integer mobileIfIndex = 100;
final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A);
- final TetherDownstream6Value value = rule.makeTetherDownstream6Value();
+ final Tether6Value value = rule.makeTether6Value();
assertEquals(value.oif, DOWNSTREAM_IFINDEX);
assertEquals(value.ethDstMac, MAC_A);
assertEquals(value.ethSrcMac, DOWNSTREAM_MAC);
@@ -790,7 +837,8 @@
coordinator.addUpstreamNameToLookupTable(ethIfIndex, ethIface);
coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface);
- final InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, 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.
@@ -810,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);
@@ -826,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.
@@ -839,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
@@ -928,6 +979,15 @@
@Test
@IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testBpfDisabledbyNoBpfUpstream6Map() throws Exception {
+ setupFunctioningNetdInterface();
+ doReturn(null).when(mDeps).getBpfUpstream6Map();
+
+ checkBpfDisabled();
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
public void testBpfDisabledbyNoBpfStatsMap() throws Exception {
setupFunctioningNetdInterface();
doReturn(null).when(mDeps).getBpfStatsMap();