diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2021-04-09 21:11:15 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2021-04-09 21:11:15 +0000 |
commit | d456f5522bdbb3115f2e3e171bc8982b549c7548 (patch) | |
tree | 4389cf4a549de7a63b6f9facefb863943e9170ac | |
parent | e8f470df66c96404ef0dd70bc87b2c26782fef9b (diff) | |
parent | 89d260c0392227074065bc039317b467366bd4f2 (diff) | |
download | Connectivity-android11-mainline-media-release.tar.gz |
Snap for 7272189 from 89d260c0392227074065bc039317b467366bd4f2 to mainline-media-releaseandroid-mainline-11.0.0_r39android11-mainline-media-release
Change-Id: I19ada9b7e5cca09b32eb9f7069e426f26180c656
31 files changed, 1141 insertions, 422 deletions
diff --git a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java index f27c831689..e310fb62f1 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 @@ -80,12 +80,14 @@ public class BpfCoordinatorShimImpl @Override public boolean startUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex, - MacAddress srcMac, MacAddress dstMac, int mtu) { + @NonNull MacAddress inDstMac, @NonNull MacAddress outSrcMac, + @NonNull MacAddress outDstMac, int mtu) { return true; } @Override - public boolean stopUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex) { + public boolean stopUpstreamIpv6Forwarding(int downstreamIfindex, + int upstreamIfindex, @NonNull MacAddress inDstMac) { return true; } @@ -171,6 +173,12 @@ public class BpfCoordinatorShimImpl } @Override + public boolean isAnyIpv4RuleOnUpstream(int ifIndex) { + /* no op */ + return false; + } + + @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 4f7fe65a6a..d7ce1392b5 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 @@ -23,6 +23,7 @@ import android.net.util.SharedLog; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; +import android.util.Log; import android.util.SparseArray; import androidx.annotation.NonNull; @@ -84,6 +85,20 @@ public class BpfCoordinatorShimImpl @Nullable private final BpfMap<TetherLimitKey, TetherLimitValue> mBpfLimitMap; + // Tracking IPv4 rule count while any rule is using the given upstream interfaces. Used for + // reducing the BPF map iteration query. The count is increased or decreased when the rule is + // added or removed successfully on mBpfDownstream4Map. Counting the rules on downstream4 map + // is because tetherOffloadRuleRemove can't get upstream interface index from upstream key, + // unless pass upstream value which is not required for deleting map entry. The upstream + // interface index is the same in Upstream4Value.oif and Downstream4Key.iif. For now, it is + // okay to count on Downstream4Key. See BpfConntrackEventConsumer#accept. + // Note that except the constructor, any calls to mBpfDownstream4Map.clear() need to clear + // this counter as well. + // TODO: Count the rule on upstream if multi-upstream is supported and the + // packet needs to be sent and responded on different upstream interfaces. + // TODO: Add IPv6 rule count. + private final SparseArray<Integer> mRule4CountOnUpstream = new SparseArray<>(); + public BpfCoordinatorShimImpl(@NonNull final Dependencies deps) { mLog = deps.getSharedLog().forSubComponent(TAG); @@ -169,12 +184,13 @@ public class BpfCoordinatorShimImpl @Override public boolean startUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex, - MacAddress srcMac, MacAddress dstMac, int mtu) { + @NonNull MacAddress inDstMac, @NonNull MacAddress outSrcMac, + @NonNull MacAddress outDstMac, 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); + final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfindex, inDstMac); + final Tether6Value value = new Tether6Value(upstreamIfindex, outSrcMac, + outDstMac, OsConstants.ETH_P_IPV6, mtu); try { mBpfUpstream6Map.insertEntry(key, value); } catch (ErrnoException | IllegalStateException e) { @@ -185,10 +201,11 @@ public class BpfCoordinatorShimImpl } @Override - public boolean stopUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex) { + public boolean stopUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex, + @NonNull MacAddress inDstMac) { if (!isInitialized()) return false; - final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfindex); + final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfindex, inDstMac); try { mBpfUpstream6Map.deleteEntry(key); } catch (ErrnoException e) { @@ -324,18 +341,22 @@ public class BpfCoordinatorShimImpl 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); + mBpfDownstream4Map.insertEntry(key, value); + + // Increase the rule count while a adding rule is using a given upstream interface. + final int upstreamIfindex = (int) key.iif; + int count = mRule4CountOnUpstream.get(upstreamIfindex, 0 /* default */); + mRule4CountOnUpstream.put(upstreamIfindex, ++count); } else { - mBpfUpstream4Map.updateEntry(key, value); + mBpfUpstream4Map.insertEntry(key, value); } } catch (ErrnoException e) { - mLog.e("Could not update entry: ", e); + mLog.e("Could not insert entry (" + key + ", " + value + "): " + e); return false; + } catch (IllegalStateException e) { + // Silent if the rule already exists. Note that the errno EEXIST was rethrown as + // IllegalStateException. See BpfMap#insertEntry. } return true; } @@ -346,7 +367,26 @@ public class BpfCoordinatorShimImpl try { if (downstream) { - mBpfDownstream4Map.deleteEntry(key); + if (!mBpfDownstream4Map.deleteEntry(key)) { + mLog.e("Could not delete entry (key: " + key + ")"); + return false; + } + + // Decrease the rule count while a deleting rule is not using a given upstream + // interface anymore. + final int upstreamIfindex = (int) key.iif; + Integer count = mRule4CountOnUpstream.get(upstreamIfindex); + if (count == null) { + Log.wtf(TAG, "Could not delete count for interface " + upstreamIfindex); + return false; + } + + if (--count == 0) { + // Remove the entry if the count decreases to zero. + mRule4CountOnUpstream.remove(upstreamIfindex); + } else { + mRule4CountOnUpstream.put(upstreamIfindex, count); + } } else { mBpfUpstream4Map.deleteEntry(key); } @@ -386,6 +426,12 @@ public class BpfCoordinatorShimImpl return true; } + @Override + public boolean isAnyIpv4RuleOnUpstream(int ifIndex) { + // No entry means no rule for the given interface because 0 has never been stored. + return mRule4CountOnUpstream.get(ifIndex) != null; + } + private String mapStatus(BpfMap m, String name) { return name + "{" + (m != null ? "OK" : "ERROR") + "}"; } diff --git a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java index b7b4c47cc3..79a628b403 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 @@ -78,21 +78,25 @@ public abstract class BpfCoordinatorShim { * @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 + * @param inDstMac the destination MAC address to use for XDP + * @param outSrcMac the source MAC address to use for packets + * @param outDstMac 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); + @NonNull MacAddress inDstMac, @NonNull MacAddress outSrcMac, + @NonNull MacAddress outDstMac, int mtu); /** * Stops IPv6 forwarding between the specified interfaces. * @param downstreamIfindex the downstream interface index * @param upstreamIfindex the upstream interface index + * @param inDstMac the destination MAC address to use for XDP * @return true if operation succeeded or was a no-op, false otherwise */ - public abstract boolean stopUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex); + public abstract boolean stopUpstreamIpv6Forwarding(int downstreamIfindex, + int upstreamIfindex, @NonNull MacAddress inDstMac); /** * Return BPF tethering offload statistics. @@ -145,6 +149,11 @@ public abstract class BpfCoordinatorShim { public abstract boolean tetherOffloadRuleRemove(boolean downstream, @NonNull Tether4Key key); /** + * Whether there is currently any IPv4 rule on the specified upstream. + */ + public abstract boolean isAnyIpv4RuleOnUpstream(int ifIndex); + + /** * Attach BPF program. * * TODO: consider using InterfaceParams to replace interface name. diff --git a/Tethering/bpf_progs/bpf_tethering.h b/Tethering/bpf_progs/bpf_tethering.h index efda228479..5fdf8cd88e 100644 --- a/Tethering/bpf_progs/bpf_tethering.h +++ b/Tethering/bpf_progs/bpf_tethering.h @@ -107,11 +107,12 @@ typedef uint64_t TetherLimitValue; // in bytes // Ethernet) have 6-byte MAC addresses. typedef struct { - uint32_t iif; // The input interface index - // TODO: extend this to include dstMac - struct in6_addr neigh6; // The destination IPv6 address + uint32_t iif; // The input interface index + uint8_t dstMac[ETH_ALEN]; // destination ethernet mac address (zeroed iff rawip ingress) + uint8_t zero[2]; // zero pad for 8 byte alignment + struct in6_addr neigh6; // The destination IPv6 address } TetherDownstream6Key; -STRUCT_SIZE(TetherDownstream6Key, 4 + 16); // 20 +STRUCT_SIZE(TetherDownstream6Key, 4 + 6 + 2 + 16); // 28 typedef struct { uint32_t oif; // The output interface to redirect to @@ -154,10 +155,12 @@ STRUCT_SIZE(TetherDownstream64Value, 4 + 14 + 2 + 4 + 4 + 2 + 2 + 8); // 40 #define TETHER_UPSTREAM6_MAP_PATH BPF_PATH_TETHER "map_offload_tether_upstream6_map" typedef struct { - uint32_t iif; // The input interface index - // TODO: extend this to include dstMac and src ip /64 subnet + uint32_t iif; // The input interface index + uint8_t dstMac[ETH_ALEN]; // destination ethernet mac address (zeroed iff rawip ingress) + uint8_t zero[2]; // zero pad for 8 byte alignment + // TODO: extend this to include src ip /64 subnet } TetherUpstream6Key; -STRUCT_SIZE(TetherUpstream6Key, 4); +STRUCT_SIZE(TetherUpstream6Key, 12); #define TETHER_DOWNSTREAM4_TC_PROG_RAWIP_NAME "prog_offload_schedcls_tether_downstream4_rawip" #define TETHER_DOWNSTREAM4_TC_PROG_ETHER_NAME "prog_offload_schedcls_tether_downstream4_ether" diff --git a/Tethering/bpf_progs/offload.c b/Tethering/bpf_progs/offload.c index 5f29d4f60a..36f6783dc2 100644 --- a/Tethering/bpf_progs/offload.c +++ b/Tethering/bpf_progs/offload.c @@ -107,18 +107,25 @@ DEFINE_BPF_MAP_GRW(tether_upstream6_map, HASH, TetherUpstream6Key, Tether6Value, static inline __always_inline int do_forward6(struct __sk_buff* skb, const bool is_ethernet, const bool downstream) { + // Must be meta-ethernet IPv6 frame + if (skb->protocol != htons(ETH_P_IPV6)) return TC_ACT_OK; + + // Require ethernet dst mac address to be our unicast address. + if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_OK; + const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0; + + // Since the program never writes via DPA (direct packet access) auto-pull/unclone logic does + // not trigger and thus we need to manually make sure we can read packet headers via DPA. + // Note: this is a blind best effort pull, which may fail or pull less - this doesn't matter. + // It has to be done early cause it will invalidate any skb->data/data_end derived pointers. + try_make_readable(skb, l2_header_size + IP6_HLEN + TCP_HLEN); + 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; - // Must have (ethernet and) ipv6 header if (data + l2_header_size + sizeof(*ip6) > data_end) return TC_ACT_OK; @@ -169,6 +176,7 @@ static inline __always_inline int do_forward6(struct __sk_buff* skb, const bool TetherUpstream6Key ku = { .iif = skb->ifindex, }; + if (is_ethernet) __builtin_memcpy(downstream ? kd.dstMac : ku.dstMac, eth->h_dest, ETH_ALEN); Tether6Value* v = downstream ? bpf_tether_downstream6_map_lookup_elem(&kd) : bpf_tether_upstream6_map_lookup_elem(&ku); @@ -346,18 +354,25 @@ DEFINE_BPF_MAP_GRW(tether_upstream4_map, HASH, Tether4Key, Tether4Value, 1024, A 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; + const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0; + + // Since the program never writes via DPA (direct packet access) auto-pull/unclone logic does + // not trigger and thus we need to manually make sure we can read packet headers via DPA. + // Note: this is a blind best effort pull, which may fail or pull less - this doesn't matter. + // It has to be done early cause it will invalidate any skb->data/data_end derived pointers. + try_make_readable(skb, l2_header_size + IP4_HLEN + TCP_HLEN); + + 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; + // Must have (ethernet and) ipv4 header if (data + l2_header_size + sizeof(*ip) > data_end) return TC_ACT_OK; @@ -474,7 +489,7 @@ static inline __always_inline int do_forward4(struct __sk_buff* skb, const bool .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]; + if (is_ethernet) __builtin_memcpy(k.dstMac, eth->h_dest, ETH_ALEN); Tether4Value* v = downstream ? bpf_tether_downstream4_map_lookup_elem(&k) : bpf_tether_upstream4_map_lookup_elem(&k); diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java index e5380e008d..da15fa8862 100644 --- a/Tethering/src/android/net/ip/IpServer.java +++ b/Tethering/src/android/net/ip/IpServer.java @@ -742,16 +742,14 @@ public class IpServer extends StateMachine { params.dnses.add(dnsServer); } } - - // Add upstream index to name mapping for the tether stats usage in the coordinator. - // Although this mapping could be added by both class Tethering and IpServer, adding - // mapping from IpServer guarantees that the mapping is added before the adding - // forwarding rules. That is because there are different state machines in both - // classes. It is hard to guarantee the link property update order between multiple - // state machines. - mBpfCoordinator.addUpstreamNameToLookupTable(upstreamIfIndex, upstreamIface); } + // Add upstream index to name mapping. See the comment of the interface mapping update in + // CMD_TETHER_CONNECTION_CHANGED. Adding the mapping update here to the avoid potential + // timing issue. It prevents that the IPv6 capability is updated later than + // CMD_TETHER_CONNECTION_CHANGED. + mBpfCoordinator.addUpstreamNameToLookupTable(upstreamIfIndex, upstreamIface); + // If v6only is null, we pass in null to setRaParams(), which handles // deprecation of any existing RA data. @@ -1335,6 +1333,26 @@ public class IpServer extends StateMachine { mUpstreamIfaceSet = newUpstreamIfaceSet; for (String ifname : added) { + // Add upstream index to name mapping for the tether stats usage in the + // coordinator. Although this mapping could be added by both class + // Tethering and IpServer, adding mapping from IpServer guarantees that + // the mapping is added before adding forwarding rules. That is because + // there are different state machines in both classes. It is hard to + // guarantee the link property update order between multiple state machines. + // Note that both IPv4 and IPv6 interface may be added because + // Tethering::setUpstreamNetwork calls getTetheringInterfaces which merges + // IPv4 and IPv6 interface name (if any) into an InterfaceSet. The IPv6 + // capability may be updated later. In that case, IPv6 interface mapping is + // updated in updateUpstreamIPv6LinkProperties. + if (!ifname.startsWith("v4-")) { // ignore clat interfaces + final InterfaceParams upstreamIfaceParams = + mDeps.getInterfaceParams(ifname); + if (upstreamIfaceParams != null) { + mBpfCoordinator.addUpstreamNameToLookupTable( + upstreamIfaceParams.index, ifname); + } + } + mBpfCoordinator.maybeAttachProgram(mIfaceName, ifname); try { mNetd.tetherAddForward(mIfaceName, ifname); diff --git a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java index afccd0a5d9..543a5c722f 100644 --- a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java +++ b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java @@ -16,7 +16,6 @@ package android.net.ip; -import static android.net.util.NetworkConstants.IPV6_MIN_MTU; import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH; import static android.net.util.TetheringUtils.getAllNodesForScopeId; import static android.system.OsConstants.AF_INET6; @@ -25,8 +24,18 @@ import static android.system.OsConstants.SOCK_RAW; import static android.system.OsConstants.SOL_SOCKET; import static android.system.OsConstants.SO_SNDTIMEO; +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA; +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_RA_HEADER_LEN; +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION; +import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU; +import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS; +import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK; +import static com.android.net.module.util.NetworkStackConstants.TAG_SYSTEM_NEIGHBOR; + import android.net.IpPrefix; import android.net.LinkAddress; +import android.net.MacAddress; import android.net.TrafficStats; import android.net.util.InterfaceParams; import android.net.util.SocketUtils; @@ -37,7 +46,12 @@ import android.system.StructTimeval; import android.util.Log; import com.android.internal.annotations.GuardedBy; -import com.android.net.module.util.NetworkStackConstants; +import com.android.net.module.util.structs.Icmpv6Header; +import com.android.net.module.util.structs.LlaOption; +import com.android.net.module.util.structs.MtuOption; +import com.android.net.module.util.structs.PrefixInformationOption; +import com.android.net.module.util.structs.RaHeader; +import com.android.net.module.util.structs.RdnssOption; import java.io.FileDescriptor; import java.io.IOException; @@ -69,9 +83,6 @@ import java.util.concurrent.atomic.AtomicInteger; */ public class RouterAdvertisementDaemon { private static final String TAG = RouterAdvertisementDaemon.class.getSimpleName(); - private static final byte ICMPV6_ND_ROUTER_SOLICIT = asByte(133); - private static final byte ICMPV6_ND_ROUTER_ADVERT = asByte(134); - private static final int MIN_RA_HEADER_SIZE = 16; // Summary of various timers and lifetimes. private static final int MIN_RTR_ADV_INTERVAL_SEC = 300; @@ -366,54 +377,27 @@ public class RouterAdvertisementDaemon { } private static void putHeader(ByteBuffer ra, boolean hasDefaultRoute, byte hopLimit) { - /** - Router Advertisement Message Format - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type | Code | Checksum | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Cur Hop Limit |M|O|H|Prf|P|R|R| Router Lifetime | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Reachable Time | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Retrans Timer | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Options ... - +-+-+-+-+-+-+-+-+-+-+-+- - */ - ra.put(ICMPV6_ND_ROUTER_ADVERT) - .put(asByte(0)) - .putShort(asShort(0)) - .put(hopLimit) - // RFC 4191 "high" preference, iff. advertising a default route. - .put(hasDefaultRoute ? asByte(0x08) : asByte(0)) - .putShort(hasDefaultRoute ? asShort(DEFAULT_LIFETIME) : asShort(0)) - .putInt(0) - .putInt(0); + // RFC 4191 "high" preference, iff. advertising a default route. + final byte flags = hasDefaultRoute ? asByte(0x08) : asByte(0); + final short lifetime = hasDefaultRoute ? asShort(DEFAULT_LIFETIME) : asShort(0); + final Icmpv6Header icmpv6Header = + new Icmpv6Header(asByte(ICMPV6_ROUTER_ADVERTISEMENT) /* type */, + asByte(0) /* code */, asShort(0) /* checksum */); + final RaHeader raHeader = new RaHeader(hopLimit, flags, lifetime, 0 /* reachableTime */, + 0 /* retransTimer */); + icmpv6Header.writeToByteBuffer(ra); + raHeader.writeToByteBuffer(ra); } private static void putSlla(ByteBuffer ra, byte[] slla) { - /** - Source/Target Link-layer Address - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type | Length | Link-Layer Address ... - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ if (slla == null || slla.length != 6) { // Only IEEE 802.3 6-byte addresses are supported. return; } - final byte nd_option_slla = 1; - final byte slla_num_8octets = 1; - ra.put(nd_option_slla) - .put(slla_num_8octets) - .put(slla); + final ByteBuffer sllaOption = LlaOption.build(asByte(ICMPV6_ND_OPTION_SLLA), + MacAddress.fromBytes(slla)); + ra.put(sllaOption); } private static void putExpandedFlagsOption(ByteBuffer ra) { @@ -439,70 +423,24 @@ public class RouterAdvertisementDaemon { } private static void putMtu(ByteBuffer ra, int mtu) { - /** - MTU - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type | Length | Reserved | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | MTU | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ - final byte nd_option_mtu = 5; - final byte mtu_num_8octs = 1; - ra.put(nd_option_mtu) - .put(mtu_num_8octs) - .putShort(asShort(0)) - .putInt((mtu < IPV6_MIN_MTU) ? IPV6_MIN_MTU : mtu); + final ByteBuffer mtuOption = MtuOption.build((mtu < IPV6_MIN_MTU) ? IPV6_MIN_MTU : mtu); + ra.put(mtuOption); } private static void putPio(ByteBuffer ra, IpPrefix ipp, int validTime, int preferredTime) { - /** - Prefix Information - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type | Length | Prefix Length |L|A| Reserved1 | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Valid Lifetime | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Preferred Lifetime | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Reserved2 | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | | - + + - | | - + Prefix + - | | - + + - | | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ final int prefixLength = ipp.getPrefixLength(); if (prefixLength != 64) { return; } - final byte nd_option_pio = 3; - final byte pio_num_8octets = 4; if (validTime < 0) validTime = 0; if (preferredTime < 0) preferredTime = 0; if (preferredTime > validTime) preferredTime = validTime; - final byte[] addr = ipp.getAddress().getAddress(); - ra.put(nd_option_pio) - .put(pio_num_8octets) - .put(asByte(prefixLength)) - .put(asByte(0xc0)) /* L & A set */ - .putInt(validTime) - .putInt(preferredTime) - .putInt(0) - .put(addr); + final ByteBuffer pioOption = PrefixInformationOption.build(ipp, + asByte(PIO_FLAG_ON_LINK | PIO_FLAG_AUTONOMOUS), validTime, preferredTime); + ra.put(pioOption); } private static void putRio(ByteBuffer ra, IpPrefix ipp) { @@ -543,22 +481,6 @@ public class RouterAdvertisementDaemon { } private static void putRdnss(ByteBuffer ra, Set<Inet6Address> dnses, int lifetime) { - /** - Recursive DNS Server (RDNSS) Option - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type | Length | Reserved | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Lifetime | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | | - : Addresses of IPv6 Recursive DNS Servers : - | | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ - final HashSet<Inet6Address> filteredDnses = new HashSet<>(); for (Inet6Address dns : dnses) { if ((new LinkAddress(dns, RFC7421_PREFIX_LENGTH)).isGlobalPreferred()) { @@ -567,29 +489,22 @@ public class RouterAdvertisementDaemon { } if (filteredDnses.isEmpty()) return; - final byte nd_option_rdnss = 25; - final byte rdnss_num_8octets = asByte(dnses.size() * 2 + 1); - ra.put(nd_option_rdnss) - .put(rdnss_num_8octets) - .putShort(asShort(0)) - .putInt(lifetime); - - for (Inet6Address dns : filteredDnses) { - // NOTE: If the full of list DNS servers doesn't fit in the packet, - // this code will cause a buffer overflow and the RA won't include - // this instance of the option at all. - // - // TODO: Consider looking at ra.remaining() to determine how many - // DNS servers will fit, and adding only those. - ra.put(dns.getAddress()); - } + final Inet6Address[] dnsesArray = + filteredDnses.toArray(new Inet6Address[filteredDnses.size()]); + final ByteBuffer rdnssOption = RdnssOption.build(lifetime, dnsesArray); + // NOTE: If the full of list DNS servers doesn't fit in the packet, + // this code will cause a buffer overflow and the RA won't include + // this instance of the option at all. + // + // TODO: Consider looking at ra.remaining() to determine how many + // DNS servers will fit, and adding only those. + ra.put(rdnssOption); } private boolean createSocket() { final int send_timout_ms = 300; - final int oldTag = TrafficStats.getAndSetThreadStatsTag( - NetworkStackConstants.TAG_SYSTEM_NEIGHBOR); + final int oldTag = TrafficStats.getAndSetThreadStatsTag(TAG_SYSTEM_NEIGHBOR); try { mSocket = Os.socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); // Setting SNDTIMEO is purely for defensive purposes. @@ -639,7 +554,7 @@ public class RouterAdvertisementDaemon { try { synchronized (mLock) { - if (mRaLength < MIN_RA_HEADER_SIZE) { + if (mRaLength < ICMPV6_RA_HEADER_LEN) { // No actual RA to send. return; } @@ -668,7 +583,7 @@ public class RouterAdvertisementDaemon { final int rval = Os.recvfrom( mSocket, mSolicitation, 0, mSolicitation.length, 0, mSolicitor); // Do the least possible amount of validation. - if (rval < 1 || mSolicitation[0] != ICMPV6_ND_ROUTER_SOLICIT) { + if (rval < 1 || mSolicitation[0] != asByte(ICMPV6_ROUTER_SOLICITATION)) { continue; } } catch (ErrnoException | SocketException e) { @@ -721,7 +636,7 @@ public class RouterAdvertisementDaemon { private int getNextMulticastTransmitDelaySec() { boolean deprecationInProgress = false; synchronized (mLock) { - if (mRaLength < MIN_RA_HEADER_SIZE) { + if (mRaLength < ICMPV6_RA_HEADER_LEN) { // No actual RA to send; just sleep for 1 day. return DAY_IN_SECONDS; } diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java index 7fb73b4904..add4f37367 100644 --- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java +++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java @@ -137,6 +137,8 @@ public class BpfCoordinator { private final BpfTetherStatsProvider mStatsProvider; @NonNull private final BpfCoordinatorShim mBpfCoordinatorShim; + @NonNull + private final BpfConntrackEventConsumer mBpfConntrackEventConsumer; // True if BPF offload is supported, false otherwise. The BPF offload could be disabled by // a runtime resource overlay package or device configuration. This flag is only initialized @@ -248,6 +250,11 @@ public class BpfCoordinator { return new ConntrackMonitor(getHandler(), getSharedLog(), consumer); } + /** Get interface information for a given interface. */ + @NonNull public InterfaceParams getInterfaceParams(String ifName) { + return InterfaceParams.getByName(ifName); + } + /** * Check OS Build at least S. * @@ -339,7 +346,14 @@ public class BpfCoordinator { mNetd = mDeps.getNetd(); mLog = mDeps.getSharedLog().forSubComponent(TAG); mIsBpfEnabled = isBpfEnabled(); - mConntrackMonitor = mDeps.getConntrackMonitor(new BpfConntrackEventConsumer()); + + // The conntrack consummer needs to be initialized in BpfCoordinator constructor because it + // have to access the data members of BpfCoordinator which is not a static class. The + // consumer object is also needed for initializing the conntrack monitor which may be + // mocked for testing. + mBpfConntrackEventConsumer = new BpfConntrackEventConsumer(); + mConntrackMonitor = mDeps.getConntrackMonitor(mBpfConntrackEventConsumer); + BpfTetherStatsProvider provider = new BpfTetherStatsProvider(); try { mDeps.getNetworkStatsManager().registerNetworkStatsProvider( @@ -478,25 +492,14 @@ public class BpfCoordinator { LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(ipServer); // 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 - // the upstream may not want to blow through the data limit that was told to apply. - // TODO: Perhaps stop the coordinator. - boolean success = updateDataLimit(upstreamIfindex); - if (!success) { - final String iface = mInterfaceNames.get(upstreamIfindex); - mLog.e("Setting data limit for " + iface + " failed."); - } - - } + maybeSetLimit(rule.upstreamIfindex); 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, + if (!mBpfCoordinatorShim.startUpstreamIpv6Forwarding(downstream, upstream, rule.srcMac, 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)); @@ -537,29 +540,15 @@ public class BpfCoordinator { if (!isAnyRuleFromDownstreamToUpstream(rule.downstreamIfindex, rule.upstreamIfindex)) { final int downstream = rule.downstreamIfindex; final int upstream = rule.upstreamIfindex; - if (!mBpfCoordinatorShim.stopUpstreamIpv6Forwarding(downstream, upstream)) { + if (!mBpfCoordinatorShim.stopUpstreamIpv6Forwarding(downstream, upstream, + rule.srcMac)) { 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)) { - final TetherStatsValue statsValue = - mBpfCoordinatorShim.tetherOffloadGetAndClearStats(upstreamIfindex); - if (statsValue == null) { - Log.wtf(TAG, "Fail to cleanup tether stats for upstream index " + upstreamIfindex); - return; - } - - SparseArray<TetherStatsValue> tetherStatsList = new SparseArray<TetherStatsValue>(); - tetherStatsList.put(upstreamIfindex, statsValue); - - // Update the last stats delta and delete the local cache for a given upstream. - updateQuotaAndStatsFromSnapshot(tetherStatsList); - mStats.remove(upstreamIfindex); - } + maybeClearLimit(rule.upstreamIfindex); } /** @@ -687,7 +676,7 @@ public class BpfCoordinator { if (lp == null || !lp.hasIpv4Address()) return; // Support raw ip upstream interface only. - final InterfaceParams params = InterfaceParams.getByName(lp.getInterfaceName()); + final InterfaceParams params = mDeps.getInterfaceParams(lp.getInterfaceName()); if (params == null || params.hasMacAddress) return; Collection<InetAddress> addresses = lp.getAddresses(); @@ -821,8 +810,8 @@ public class BpfCoordinator { } 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), + return String.format("%d(%s) %s -> %d(%s) %04x %s %s", + key.iif, getIfName(key.iif), key.dstMac, value.oif, getIfName(value.oif), value.ethProto, value.ethSrcMac, value.ethDstMac); } @@ -912,6 +901,62 @@ public class BpfCoordinator { /** IPv6 forwarding rule class. */ public static class Ipv6ForwardingRule { + // The upstream6 and downstream6 rules are built as the following tables. Only raw ip + // upstream interface is supported. + // TODO: support ether ip upstream interface. + // + // NAT network topology: + // + // public network (rawip) private network + // | UE | + // +------------+ V +------------+------------+ V +------------+ + // | Sever +---------+ Upstream | Downstream +---------+ Client | + // +------------+ +------------+------------+ +------------+ + // + // upstream6 key and value: + // + // +------+-------------+ + // | TetherUpstream6Key | + // +------+------+------+ + // |field |iif |dstMac| + // | | | | + // +------+------+------+ + // |value |downst|downst| + // | |ream |ream | + // +------+------+------+ + // + // +------+----------------------------------+ + // | |Tether6Value | + // +------+------+------+------+------+------+ + // |field |oif |ethDst|ethSrc|ethPro|pmtu | + // | | |mac |mac |to | | + // +------+------+------+------+------+------+ + // |value |upstre|-- |-- |ETH_P_|1500 | + // | |am | | |IP | | + // +------+------+------+------+------+------+ + // + // downstream6 key and value: + // + // +------+--------------------+ + // | |TetherDownstream6Key| + // +------+------+------+------+ + // |field |iif |dstMac|neigh6| + // | | | | | + // +------+------+------+------+ + // |value |upstre|-- |client| + // | |am | | | + // +------+------+------+------+ + // + // +------+----------------------------------+ + // | |Tether6Value | + // +------+------+------+------+------+------+ + // |field |oif |ethDst|ethSrc|ethPro|pmtu | + // | | |mac |mac |to | | + // +------+------+------+------+------+------+ + // |value |downst|client|downst|ETH_P_|1500 | + // | |ream | |ream |IP | | + // +------+------+------+------+------+------+ + // public final int upstreamIfindex; public final int downstreamIfindex; @@ -961,7 +1006,8 @@ public class BpfCoordinator { */ @NonNull public TetherDownstream6Key makeTetherDownstream6Key() { - return new TetherDownstream6Key(upstreamIfindex, address.getAddress()); + return new TetherDownstream6Key(upstreamIfindex, NULL_MAC_ADDRESS, + address.getAddress()); } /** @@ -1114,7 +1160,10 @@ public class BpfCoordinator { // Support raw ip only. // TODO: add ether ip support. - private class BpfConntrackEventConsumer implements ConntrackEventConsumer { + // TODO: parse CTA_PROTOINFO of conntrack event in ConntrackMonitor. For TCP, only add rules + // while TCP status is established. + @VisibleForTesting + class BpfConntrackEventConsumer implements ConntrackEventConsumer { @NonNull private Tether4Key makeTetherUpstream4Key( @NonNull ConntrackEvent e, @NonNull ClientInfo c) { @@ -1179,8 +1228,9 @@ public class BpfCoordinator { if (e.msgType == (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8 | NetlinkConstants.IPCTNL_MSG_CT_DELETE)) { - mBpfCoordinatorShim.tetherOffloadRuleRemove(false, upstream4Key); - mBpfCoordinatorShim.tetherOffloadRuleRemove(true, downstream4Key); + mBpfCoordinatorShim.tetherOffloadRuleRemove(UPSTREAM, upstream4Key); + mBpfCoordinatorShim.tetherOffloadRuleRemove(DOWNSTREAM, downstream4Key); + maybeClearLimit(upstreamIndex); return; } @@ -1188,8 +1238,9 @@ public class BpfCoordinator { final Tether4Value downstream4Value = makeTetherDownstream4Value(e, tetherClient, upstreamIndex); - mBpfCoordinatorShim.tetherOffloadRuleAdd(false, upstream4Key, upstream4Value); - mBpfCoordinatorShim.tetherOffloadRuleAdd(true, downstream4Key, downstream4Value); + maybeSetLimit(upstreamIndex); + mBpfCoordinatorShim.tetherOffloadRuleAdd(UPSTREAM, upstream4Key, upstream4Value); + mBpfCoordinatorShim.tetherOffloadRuleAdd(DOWNSTREAM, downstream4Key, downstream4Value); } } @@ -1251,6 +1302,47 @@ public class BpfCoordinator { return sendDataLimitToBpfMap(ifIndex, quotaBytes); } + private void maybeSetLimit(int upstreamIfindex) { + if (isAnyRuleOnUpstream(upstreamIfindex) + || mBpfCoordinatorShim.isAnyIpv4RuleOnUpstream(upstreamIfindex)) { + return; + } + + // If failed to set a data limit, probably should not use this upstream, because + // the upstream may not want to blow through the data limit that was told to apply. + // TODO: Perhaps stop the coordinator. + boolean success = updateDataLimit(upstreamIfindex); + if (!success) { + final String iface = mInterfaceNames.get(upstreamIfindex); + mLog.e("Setting data limit for " + iface + " failed."); + } + } + + // TODO: This should be also called while IpServer wants to clear all IPv4 rules. Relying on + // conntrack event can't cover this case. + private void maybeClearLimit(int upstreamIfindex) { + if (isAnyRuleOnUpstream(upstreamIfindex) + || mBpfCoordinatorShim.isAnyIpv4RuleOnUpstream(upstreamIfindex)) { + return; + } + + final TetherStatsValue statsValue = + mBpfCoordinatorShim.tetherOffloadGetAndClearStats(upstreamIfindex); + if (statsValue == null) { + Log.wtf(TAG, "Fail to cleanup tether stats for upstream index " + upstreamIfindex); + return; + } + + SparseArray<TetherStatsValue> tetherStatsList = new SparseArray<TetherStatsValue>(); + tetherStatsList.put(upstreamIfindex, statsValue); + + // Update the last stats delta and delete the local cache for a given upstream. + updateQuotaAndStatsFromSnapshot(tetherStatsList); + mStats.remove(upstreamIfindex); + } + + // TODO: Rename to isAnyIpv6RuleOnUpstream and define an isAnyRuleOnUpstream method that called + // both isAnyIpv6RuleOnUpstream and mBpfCoordinatorShim.isAnyIpv4RuleOnUpstream. private boolean isAnyRuleOnUpstream(int upstreamIfindex) { for (LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules : mIpv6ForwardingRules .values()) { @@ -1420,5 +1512,13 @@ public class BpfCoordinator { return mInterfaceNames; } + // Return BPF conntrack event consumer. This is used for testing only. + // Note that this can be only called on handler thread. + @NonNull + @VisibleForTesting + final BpfConntrackEventConsumer getBpfConntrackEventConsumerForTesting() { + return mBpfConntrackEventConsumer; + } + 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 e9b4ccf2f4..1363dc5150 100644 --- a/Tethering/src/com/android/networkstack/tethering/BpfMap.java +++ b/Tethering/src/com/android/networkstack/tethering/BpfMap.java @@ -98,6 +98,7 @@ public class BpfMap<K extends Struct, V extends Struct> implements AutoCloseable /** * Update an existing or create a new key -> value entry in an eBbpf map. + * (use insertOrReplaceEntry() if you need to know whether insert or replace happened) */ public void updateEntry(K key, V value) throws ErrnoException { writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_ANY); @@ -133,6 +134,35 @@ public class BpfMap<K extends Struct, V extends Struct> implements AutoCloseable } } + /** + * Update an existing or create a new key -> value entry in an eBbpf map. + * Returns true if inserted, false if replaced. + * (use updateEntry() if you don't care whether insert or replace happened) + * Note: see inline comment below if running concurrently with delete operations. + */ + public boolean insertOrReplaceEntry(K key, V value) + throws ErrnoException { + try { + writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_NOEXIST); + return true; /* insert succeeded */ + } catch (ErrnoException e) { + if (e.errno != EEXIST) throw e; + } + try { + writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_EXIST); + return false; /* replace succeeded */ + } catch (ErrnoException e) { + if (e.errno != ENOENT) throw e; + } + /* If we reach here somebody deleted after our insert attempt and before our replace: + * this implies a race happened. The kernel bpf delete interface only takes a key, + * and not the value, so we can safely pretend the replace actually succeeded and + * was immediately followed by the other thread's delete, since the delete cannot + * observe the potential change to the value. + */ + return false; /* pretend replace succeeded */ + } + /** Remove existing key from eBpf map. Return false if map was not modified. */ public boolean deleteEntry(K key) throws ErrnoException { return deleteMapEntry(mMapFd, key.writeToBytes()); diff --git a/Tethering/src/com/android/networkstack/tethering/TetherDownstream6Key.java b/Tethering/src/com/android/networkstack/tethering/TetherDownstream6Key.java index 3860cba745..a08ad4ab03 100644 --- a/Tethering/src/com/android/networkstack/tethering/TetherDownstream6Key.java +++ b/Tethering/src/com/android/networkstack/tethering/TetherDownstream6Key.java @@ -16,6 +16,10 @@ 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; @@ -24,16 +28,23 @@ import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; +import java.util.Objects; /** The key of BpfMap which is used for bpf offload. */ 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) + @Field(order = 1, type = Type.EUI48, padding = 2) + public final MacAddress dstMac; // Destination ethernet mac address (zeroed iff rawip ingress). + + @Field(order = 2, type = Type.ByteArray, arraysize = 16) public final byte[] neigh6; // The destination IPv6 address. - public TetherDownstream6Key(final long iif, final byte[] neigh6) { + public TetherDownstream6Key(final long iif, @NonNull final MacAddress dstMac, + final byte[] neigh6) { + Objects.requireNonNull(dstMac); + try { final Inet6Address unused = (Inet6Address) InetAddress.getByAddress(neigh6); } catch (ClassCastException | UnknownHostException e) { @@ -41,29 +52,15 @@ public class TetherDownstream6Key extends Struct { + Arrays.toString(neigh6)); } this.iif = iif; + this.dstMac = dstMac; this.neigh6 = neigh6; } @Override - public boolean equals(Object obj) { - if (this == obj) return true; - - if (!(obj instanceof TetherDownstream6Key)) return false; - - final TetherDownstream6Key that = (TetherDownstream6Key) obj; - - return iif == that.iif && Arrays.equals(neigh6, that.neigh6); - } - - @Override - public int hashCode() { - return Long.hashCode(iif) ^ Arrays.hashCode(neigh6); - } - - @Override public String toString() { try { - return String.format("iif: %d, neigh: %s", iif, Inet6Address.getByAddress(neigh6)); + return String.format("iif: %d, dstMac: %s, neigh: %s", iif, dstMac, + Inet6Address.getByAddress(neigh6)); } catch (UnknownHostException e) { // Should not happen because construtor already verify neigh6. 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 index c736f2a883..5893885a76 100644 --- a/Tethering/src/com/android/networkstack/tethering/TetherUpstream6Key.java +++ b/Tethering/src/com/android/networkstack/tethering/TetherUpstream6Key.java @@ -16,14 +16,26 @@ package com.android.networkstack.tethering; +import android.net.MacAddress; + +import androidx.annotation.NonNull; + import com.android.net.module.util.Struct; +import java.util.Objects; + /** 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) { + @Field(order = 1, type = Type.EUI48, padding = 2) + public final MacAddress dstMac; // Destination ethernet mac address (zeroed iff rawip ingress). + + public TetherUpstream6Key(int iif, @NonNull final MacAddress dstMac) { + Objects.requireNonNull(dstMac); + this.iif = iif; + this.dstMac = dstMac; } } diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java index c6e8fd631f..f795747c66 100644 --- a/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -1719,6 +1719,12 @@ public class Tethering { break; } + if (mConfig.chooseUpstreamAutomatically + && arg1 == UpstreamNetworkMonitor.EVENT_DEFAULT_SWITCHED) { + chooseUpstreamType(true); + return; + } + if (ns == null || !pertainsToCurrentUpstream(ns)) { // TODO: In future, this is where upstream evaluation and selection // could be handled for notifications which include sufficient data. diff --git a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java index 513f07c735..e39145bba4 100644 --- a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java +++ b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java @@ -42,11 +42,14 @@ import android.os.Handler; import android.util.Log; import android.util.SparseIntArray; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.StateMachine; import java.util.HashMap; import java.util.HashSet; +import java.util.Objects; import java.util.Set; @@ -82,6 +85,7 @@ public class UpstreamNetworkMonitor { public static final int EVENT_ON_CAPABILITIES = 1; public static final int EVENT_ON_LINKPROPERTIES = 2; public static final int EVENT_ON_LOST = 3; + public static final int EVENT_DEFAULT_SWITCHED = 4; public static final int NOTIFY_LOCAL_PREFIXES = 10; // This value is used by deprecated preferredUpstreamIfaceTypes selection which is default // disabled. @@ -398,7 +402,7 @@ public class UpstreamNetworkMonitor { notifyTarget(EVENT_ON_CAPABILITIES, network); } - private void handleLinkProp(Network network, LinkProperties newLp) { + private void updateLinkProperties(Network network, LinkProperties newLp) { final UpstreamNetworkState prev = mNetworkMap.get(network); if (prev == null || newLp.equals(prev.linkProperties)) { // Ignore notifications about networks for which we have not yet @@ -414,8 +418,10 @@ public class UpstreamNetworkMonitor { mNetworkMap.put(network, new UpstreamNetworkState( newLp, prev.networkCapabilities, network)); - // TODO: If sufficient information is available to select a more - // preferable upstream, do so now and notify the target. + } + + private void handleLinkProp(Network network, LinkProperties newLp) { + updateLinkProperties(network, newLp); notifyTarget(EVENT_ON_LINKPROPERTIES, network); } @@ -445,6 +451,24 @@ public class UpstreamNetworkMonitor { notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network)); } + private void maybeHandleNetworkSwitch(@NonNull Network network) { + if (Objects.equals(mDefaultInternetNetwork, network)) return; + + final UpstreamNetworkState ns = mNetworkMap.get(network); + if (ns == null) { + // Can never happen unless there is a bug in ConnectivityService. Entries are only + // removed from mNetworkMap when receiving onLost, and onLost for a given network can + // never be followed by any other callback on that network. + Log.wtf(TAG, "maybeHandleNetworkSwitch: no UpstreamNetworkState for " + network); + return; + } + + // Default network changed. Update local data and notify tethering. + Log.d(TAG, "New default Internet network: " + network); + mDefaultInternetNetwork = network; + notifyTarget(EVENT_DEFAULT_SWITCHED, ns); + } + private void recomputeLocalPrefixes() { final HashSet<IpPrefix> localPrefixes = allLocalPrefixes(mNetworkMap.values()); if (!mLocalPrefixes.equals(localPrefixes)) { @@ -482,7 +506,22 @@ public class UpstreamNetworkMonitor { @Override public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) { if (mCallbackType == CALLBACK_DEFAULT_INTERNET) { - mDefaultInternetNetwork = network; + // mDefaultInternetNetwork is not updated here because upstream selection must only + // run when the LinkProperties have been updated as well as the capabilities. If + // this callback is due to a default network switch, then the system will invoke + // onLinkPropertiesChanged right after this method and mDefaultInternetNetwork will + // be updated then. + // + // Technically, not updating here isn't necessary, because the notifications to + // Tethering sent by notifyTarget are messages sent to a state machine running on + // the same thread as this method, and so cannot arrive until after this method has + // returned. However, it is not a good idea to rely on that because fact that + // Tethering uses multiple state machines running on the same thread is a major + // source of race conditions and something that should be fixed. + // + // TODO: is it correct that this code always updates EntitlementManager? + // This code runs when the default network connects or changes capabilities, but the + // default network might not be the tethering upstream. final boolean newIsCellular = isCellular(newNc); if (mIsDefaultCellularUpstream != newIsCellular) { mIsDefaultCellularUpstream = newIsCellular; @@ -496,7 +535,15 @@ public class UpstreamNetworkMonitor { @Override public void onLinkPropertiesChanged(Network network, LinkProperties newLp) { - if (mCallbackType == CALLBACK_DEFAULT_INTERNET) return; + if (mCallbackType == CALLBACK_DEFAULT_INTERNET) { + updateLinkProperties(network, newLp); + // When the default network callback calls onLinkPropertiesChanged, it means that + // all the network information for the default network is known (because + // onLinkPropertiesChanged is called after onAvailable and onCapabilitiesChanged). + // Inform tethering that the default network might have changed. + maybeHandleNetworkSwitch(network); + return; + } handleLinkProp(network, newLp); // Any non-LISTEN_ALL callback will necessarily concern a network that will @@ -513,6 +560,8 @@ public class UpstreamNetworkMonitor { mDefaultInternetNetwork = null; mIsDefaultCellularUpstream = false; mEntitlementMgr.notifyUpstream(false); + Log.d(TAG, "Lost default Internet network: " + network); + notifyTarget(EVENT_DEFAULT_SWITCHED, null); return; } diff --git a/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java b/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java index 42a91aa9ac..a933e1b277 100644 --- a/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java +++ b/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java @@ -21,9 +21,8 @@ import static android.system.OsConstants.IPPROTO_ICMPV6; import static com.android.net.module.util.IpUtils.icmpv6Checksum; import static com.android.net.module.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; import android.app.Instrumentation; import android.content.Context; @@ -52,13 +51,14 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; +import java.io.IOException; import java.nio.ByteBuffer; @RunWith(AndroidJUnit4.class) @SmallTest public class DadProxyTest { private static final int DATA_BUFFER_LEN = 4096; - private static final int PACKET_TIMEOUT_MS = 5_000; + private static final int PACKET_TIMEOUT_MS = 2_000; // Long enough for DAD to succeed. // Start the readers manually on a common handler shared with DadProxy, for simplicity @Rule @@ -119,16 +119,18 @@ public class DadProxyTest { } } - private void setupTapInterfaces() { + private void setupTapInterfaces() throws Exception { // Create upstream test iface. mUpstreamReader.start(mHandler); - mUpstreamParams = InterfaceParams.getByName(mUpstreamReader.iface.getInterfaceName()); + final String upstreamIface = mUpstreamReader.iface.getInterfaceName(); + mUpstreamParams = InterfaceParams.getByName(upstreamIface); assertNotNull(mUpstreamParams); mUpstreamPacketReader = mUpstreamReader.getReader(); // Create tethered test iface. mTetheredReader.start(mHandler); - mTetheredParams = InterfaceParams.getByName(mTetheredReader.getIface().getInterfaceName()); + final String tetheredIface = mTetheredReader.getIface().getInterfaceName(); + mTetheredParams = InterfaceParams.getByName(tetheredIface); assertNotNull(mTetheredParams); mTetheredPacketReader = mTetheredReader.getReader(); } @@ -224,6 +226,12 @@ public class DadProxyTest { return false; } + private ByteBuffer copy(ByteBuffer buf) { + // There does not seem to be a way to copy ByteBuffers. ByteBuffer does not implement + // clone() and duplicate() copies the metadata but shares the contents. + return ByteBuffer.wrap(buf.array().clone()); + } + private void updateDstMac(ByteBuffer buf, MacAddress mac) { buf.put(mac.toByteArray()); buf.rewind(); @@ -234,14 +242,50 @@ public class DadProxyTest { buf.rewind(); } + private void receivePacketAndMaybeExpectForwarded(boolean expectForwarded, + ByteBuffer in, TapPacketReader inReader, ByteBuffer out, TapPacketReader outReader) + throws IOException { + + inReader.sendResponse(in); + if (waitForPacket(out, outReader)) return; + + // When the test runs, DAD may be in progress, because the interface has just been created. + // If so, the DAD proxy will get EADDRNOTAVAIL when trying to send packets. It is not + // possible to work around this using IPV6_FREEBIND or IPV6_TRANSPARENT options because the + // kernel rawv6 code doesn't consider those options either when binding or when sending, and + // doesn't get the source address from the packet even in IPPROTO_RAW/HDRINCL mode (it only + // gets it from the socket or from cmsg). + // + // If DAD was in progress when the above was attempted, try again and expect the packet to + // be forwarded. Don't disable DAD in the test because if we did, the test would not notice + // if, for example, the DAD proxy code just crashed if it received EADDRNOTAVAIL. + final String msg = expectForwarded + ? "Did not receive expected packet even after waiting for DAD:" + : "Unexpectedly received packet:"; + + inReader.sendResponse(in); + assertEquals(msg, expectForwarded, waitForPacket(out, outReader)); + } + + private void receivePacketAndExpectForwarded(ByteBuffer in, TapPacketReader inReader, + ByteBuffer out, TapPacketReader outReader) throws IOException { + receivePacketAndMaybeExpectForwarded(true, in, inReader, out, outReader); + } + + private void receivePacketAndExpectNotForwarded(ByteBuffer in, TapPacketReader inReader, + ByteBuffer out, TapPacketReader outReader) throws IOException { + receivePacketAndMaybeExpectForwarded(false, in, inReader, out, outReader); + } + @Test public void testNaForwardingFromUpstreamToTether() throws Exception { ByteBuffer na = createDadPacket(NeighborPacketForwarder.ICMPV6_NEIGHBOR_ADVERTISEMENT); - mUpstreamPacketReader.sendResponse(na); - updateDstMac(na, MacAddress.fromString("33:33:00:00:00:01")); - updateSrcMac(na, mTetheredParams); - assertTrue(waitForPacket(na, mTetheredPacketReader)); + ByteBuffer out = copy(na); + updateDstMac(out, MacAddress.fromString("33:33:00:00:00:01")); + updateSrcMac(out, mTetheredParams); + + receivePacketAndExpectForwarded(na, mUpstreamPacketReader, out, mTetheredPacketReader); } @Test @@ -249,19 +293,21 @@ public class DadProxyTest { public void testNaForwardingFromTetherToUpstream() throws Exception { ByteBuffer na = createDadPacket(NeighborPacketForwarder.ICMPV6_NEIGHBOR_ADVERTISEMENT); - mTetheredPacketReader.sendResponse(na); - updateDstMac(na, MacAddress.fromString("33:33:00:00:00:01")); - updateSrcMac(na, mTetheredParams); - assertFalse(waitForPacket(na, mUpstreamPacketReader)); + ByteBuffer out = copy(na); + updateDstMac(out, MacAddress.fromString("33:33:00:00:00:01")); + updateSrcMac(out, mTetheredParams); + + receivePacketAndExpectNotForwarded(na, mTetheredPacketReader, out, mUpstreamPacketReader); } @Test public void testNsForwardingFromTetherToUpstream() throws Exception { ByteBuffer ns = createDadPacket(NeighborPacketForwarder.ICMPV6_NEIGHBOR_SOLICITATION); - mTetheredPacketReader.sendResponse(ns); - updateSrcMac(ns, mUpstreamParams); - assertTrue(waitForPacket(ns, mUpstreamPacketReader)); + ByteBuffer out = copy(ns); + updateSrcMac(out, mUpstreamParams); + + receivePacketAndExpectForwarded(ns, mTetheredPacketReader, out, mUpstreamPacketReader); } @Test @@ -269,8 +315,9 @@ public class DadProxyTest { public void testNsForwardingFromUpstreamToTether() throws Exception { ByteBuffer ns = createDadPacket(NeighborPacketForwarder.ICMPV6_NEIGHBOR_SOLICITATION); - mUpstreamPacketReader.sendResponse(ns); + ByteBuffer out = copy(ns); updateSrcMac(ns, mUpstreamParams); - assertFalse(waitForPacket(ns, mTetheredPacketReader)); + + receivePacketAndExpectNotForwarded(ns, mUpstreamPacketReader, out, mTetheredPacketReader); } } diff --git a/Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java b/Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java index 14dae5c562..1d942146e1 100644 --- a/Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java +++ b/Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java @@ -16,6 +16,8 @@ package android.net.ip; +import static android.net.RouteInfo.RTN_UNICAST; + import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN; import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_MTU; @@ -27,6 +29,8 @@ import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_AD import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST; import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN; import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN; +import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS; +import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -38,7 +42,9 @@ import android.content.Context; import android.net.INetd; import android.net.IpPrefix; import android.net.MacAddress; +import android.net.RouteInfo; import android.net.ip.RouterAdvertisementDaemon.RaParams; +import android.net.shared.RouteUtils; import android.net.util.InterfaceParams; import android.os.Handler; import android.os.HandlerThread; @@ -74,6 +80,7 @@ import java.net.Inet6Address; import java.net.InetAddress; import java.nio.ByteBuffer; import java.util.HashSet; +import java.util.List; @RunWith(AndroidJUnit4.class) @SmallTest @@ -96,8 +103,6 @@ public final class RouterAdvertisementDaemonTest { @BeforeClass public static void setupOnce() { - System.loadLibrary("tetherutilsjni"); - final Instrumentation inst = InstrumentationRegistry.getInstrumentation(); final IBinder netdIBinder = (IBinder) inst.getContext().getSystemService(Context.NETD_SERVICE); @@ -151,7 +156,7 @@ public final class RouterAdvertisementDaemonTest { mNewParams = newParams; } - public boolean isPacketMatched(final byte[] pkt) throws Exception { + public boolean isPacketMatched(final byte[] pkt, boolean multicast) throws Exception { if (pkt.length < (ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_RA_HEADER_LEN)) { return false; } @@ -172,6 +177,15 @@ public final class RouterAdvertisementDaemonTest { final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, buf); if (icmpv6Hdr.type != (short) ICMPV6_ROUTER_ADVERTISEMENT) return false; + // Check whether IPv6 destination address is multicast or unicast + if (multicast) { + assertEquals(ipv6Hdr.dstIp, IPV6_ADDR_ALL_NODES_MULTICAST); + } else { + // The unicast IPv6 destination address in RA can be either link-local or global + // IPv6 address. This test only expects link-local address. + assertTrue(ipv6Hdr.dstIp.isLinkLocalAddress()); + } + // Parse RA header final RaHeader raHdr = Struct.parse(RaHeader.class, buf); assertEquals(mNewParams.hopLimit, raHdr.hopLimit); @@ -182,13 +196,15 @@ public final class RouterAdvertisementDaemonTest { final int length = Byte.toUnsignedInt(buf.get()); switch (type) { case ICMPV6_ND_OPTION_PIO: + // length is 4 because this test only expects one PIO included in the + // router advertisement packet. assertEquals(4, length); final ByteBuffer pioBuf = ByteBuffer.wrap(buf.array(), currentPos, Struct.getSize(PrefixInformationOption.class)); final PrefixInformationOption pio = Struct.parse(PrefixInformationOption.class, pioBuf); - assertEquals((byte) 0xc0, pio.flags); // L & A set + assertEquals((byte) (PIO_FLAG_ON_LINK | PIO_FLAG_AUTONOMOUS), pio.flags); final InetAddress address = InetAddress.getByAddress(pio.prefix); final IpPrefix prefix = new IpPrefix(address, pio.prefixLen); @@ -199,7 +215,7 @@ public final class RouterAdvertisementDaemonTest { assertEquals(0, pio.validLifetime); assertEquals(0, pio.preferredLifetime); } else { - fail("Unepxected prefix: " + prefix); + fail("Unexpected prefix: " + prefix); } // Move ByteBuffer position to the next option. @@ -261,15 +277,24 @@ public final class RouterAdvertisementDaemonTest { return params; } - private boolean assertRaPacket(final TestRaPacket testRa) - throws Exception { + private boolean isRaPacket(final TestRaPacket testRa, boolean multicast) throws Exception { byte[] packet; while ((packet = mTetheredPacketReader.poll(PACKET_TIMEOUT_MS)) != null) { - if (testRa.isPacketMatched(packet)) return true; + if (testRa.isPacketMatched(packet, multicast)) { + return true; + } } return false; } + private void assertUnicastRaPacket(final TestRaPacket testRa) throws Exception { + assertTrue(isRaPacket(testRa, false /* multicast */)); + } + + private void assertMulticastRaPacket(final TestRaPacket testRa) throws Exception { + assertTrue(isRaPacket(testRa, true /* multicast */)); + } + private ByteBuffer createRsPacket(final String srcIp) throws Exception { final MacAddress dstMac = MacAddress.fromString("33:33:03:04:05:06"); final MacAddress srcMac = mTetheredParams.macAddr; @@ -284,22 +309,36 @@ public final class RouterAdvertisementDaemonTest { assertTrue(mRaDaemon.start()); final RaParams params1 = createRaParams("2001:1122:3344::5566"); mRaDaemon.buildNewRa(null, params1); - assertRaPacket(new TestRaPacket(null, params1)); + assertMulticastRaPacket(new TestRaPacket(null, params1)); final RaParams params2 = createRaParams("2006:3344:5566::7788"); mRaDaemon.buildNewRa(params1, params2); - assertRaPacket(new TestRaPacket(params1, params2)); + assertMulticastRaPacket(new TestRaPacket(params1, params2)); } @Test public void testSolicitRouterAdvertisement() throws Exception { + // Enable IPv6 forwarding is necessary, which makes kernel process RS correctly and + // create the neighbor entry for peer's link-layer address and IPv6 address. Otherwise, + // when device receives RS with IPv6 link-local address as source address, it has to + // initiate the address resolution first before responding the unicast RA. + sNetd.setProcSysNet(INetd.IPV6, INetd.CONF, mTetheredParams.name, "forwarding", "1"); + assertTrue(mRaDaemon.start()); final RaParams params1 = createRaParams("2001:1122:3344::5566"); mRaDaemon.buildNewRa(null, params1); - assertRaPacket(new TestRaPacket(null, params1)); + assertMulticastRaPacket(new TestRaPacket(null, params1)); + + // Add a default route "fe80::/64 -> ::" to local network, otherwise, device will fail to + // send the unicast RA out due to the ENETUNREACH error(No route to the peer's link-local + // address is present). + final String iface = mTetheredParams.name; + final RouteInfo linkLocalRoute = + new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST); + RouteUtils.addRoutesToLocalNetwork(sNetd, iface, List.of(linkLocalRoute)); final ByteBuffer rs = createRsPacket("fe80::1122:3344:5566:7788"); mTetheredPacketReader.sendResponse(rs); - assertRaPacket(new TestRaPacket(null, params1)); + assertUnicastRaPacket(new TestRaPacket(null, params1)); } } 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 62302c37c8..830729d98f 100644 --- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java +++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java @@ -65,13 +65,13 @@ public final class BpfMapTest { @Before public void setUp() throws Exception { mTestData = new ArrayMap<>(); - mTestData.put(createTetherDownstream6Key(101, "2001:db8::1"), + mTestData.put(createTetherDownstream6Key(101, "00:00:00:00:00:aa", "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"), + mTestData.put(createTetherDownstream6Key(102, "00:00:00:00:00:bb", "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"), + mTestData.put(createTetherDownstream6Key(103, "00:00:00:00:00:cc", "2001:db8::3"), createTether6Value(33, "00:00:00:00:00:0e", "33:33:33:00:00:0f", ETH_P_IPV6, 1500)); @@ -94,11 +94,12 @@ public final class BpfMapTest { assertTrue(mTestMap.isEmpty()); } - private TetherDownstream6Key createTetherDownstream6Key(long iif, String address) - throws Exception { + private TetherDownstream6Key createTetherDownstream6Key(long iif, String mac, + String address) throws Exception { + final MacAddress dstMac = MacAddress.fromString(mac); final InetAddress ipv6Address = InetAddress.getByName(address); - return new TetherDownstream6Key(iif, ipv6Address.getAddress()); + return new TetherDownstream6Key(iif, dstMac, ipv6Address.getAddress()); } private Tether6Value createTether6Value(int oif, String src, String dst, int proto, int pmtu) { @@ -164,7 +165,7 @@ public final class BpfMapTest { public void testGetNextKey() throws Exception { // [1] If the passed-in key is not found on empty map, return null. final TetherDownstream6Key nonexistentKey = - createTetherDownstream6Key(1234, "2001:db8::10"); + createTetherDownstream6Key(1234, "00:00:00:00:00:01", "2001:db8::10"); assertNull(mTestMap.getNextKey(nonexistentKey)); // [2] If the passed-in key is null on empty map, throw NullPointerException. @@ -209,7 +210,7 @@ public final class BpfMapTest { } @Test - public void testUpdateBpfMap() throws Exception { + public void testUpdateEntry() throws Exception { final TetherDownstream6Key key = mTestData.keyAt(0); final Tether6Value value = mTestData.valueAt(0); final Tether6Value value2 = mTestData.valueAt(1); @@ -232,6 +233,29 @@ public final class BpfMapTest { } @Test + public void testInsertOrReplaceEntry() throws Exception { + final TetherDownstream6Key key = mTestData.keyAt(0); + final Tether6Value value = mTestData.valueAt(0); + final Tether6Value value2 = mTestData.valueAt(1); + assertFalse(mTestMap.deleteEntry(key)); + + // insertOrReplaceEntry will create an entry if it does not exist already. + assertTrue(mTestMap.insertOrReplaceEntry(key, value)); + assertTrue(mTestMap.containsKey(key)); + final Tether6Value result = mTestMap.getValue(key); + assertEquals(value, result); + + // updateEntry will update an entry that already exists. + assertFalse(mTestMap.insertOrReplaceEntry(key, value2)); + assertTrue(mTestMap.containsKey(key)); + final Tether6Value result2 = mTestMap.getValue(key); + assertEquals(value2, result2); + + assertTrue(mTestMap.deleteEntry(key)); + assertFalse(mTestMap.containsKey(key)); + } + + @Test public void testInsertReplaceEntry() throws Exception { final TetherDownstream6Key key = mTestData.keyAt(0); final Tether6Value value = mTestData.valueAt(0); @@ -344,7 +368,8 @@ public final class BpfMapTest { // 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"), + testData.put( + createTetherDownstream6Key(i, "00:00:00:00:00:01", "2001:db8::1"), createTether6Value(100, "de:ad:be:ef:00:01", "de:ad:be:ef:00:02", ETH_P_IPV6, 1500)); } diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java index adf1f671ca..435cab5140 100644 --- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java @@ -474,6 +474,8 @@ public class IpServerTest { InOrder inOrder = inOrder(mNetd, mBpfCoordinator); // Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>. + inOrder.verify(mBpfCoordinator).addUpstreamNameToLookupTable(UPSTREAM_IFINDEX, + UPSTREAM_IFACE); inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE); @@ -494,6 +496,8 @@ public class IpServerTest { inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE); // Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2>. + inOrder.verify(mBpfCoordinator).addUpstreamNameToLookupTable(UPSTREAM_IFINDEX2, + UPSTREAM_IFACE2); inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2); inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2); inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2); @@ -517,6 +521,8 @@ public class IpServerTest { // Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> and expect that failed on // tetherAddForward. + inOrder.verify(mBpfCoordinator).addUpstreamNameToLookupTable(UPSTREAM_IFINDEX2, + UPSTREAM_IFACE2); inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2); inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2); @@ -543,6 +549,8 @@ public class IpServerTest { // Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> and expect that failed on // ipfwdAddInterfaceForward. + inOrder.verify(mBpfCoordinator).addUpstreamNameToLookupTable(UPSTREAM_IFINDEX2, + UPSTREAM_IFACE2); inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2); inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2); inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2); @@ -830,8 +838,8 @@ public class IpServerTest { @NonNull private static TetherDownstream6Key makeDownstream6Key(int upstreamIfindex, - @NonNull final InetAddress dst) { - return new TetherDownstream6Key(upstreamIfindex, dst.getAddress()); + @NonNull MacAddress upstreamMac, @NonNull final InetAddress dst) { + return new TetherDownstream6Key(upstreamIfindex, upstreamMac, dst.getAddress()); } @NonNull @@ -849,10 +857,12 @@ public class IpServerTest { } private void verifyTetherOffloadRuleAdd(@Nullable InOrder inOrder, int upstreamIfindex, - @NonNull final InetAddress dst, @NonNull final MacAddress dstMac) throws Exception { + @NonNull MacAddress upstreamMac, @NonNull final InetAddress dst, + @NonNull final MacAddress dstMac) throws Exception { if (mBpfDeps.isAtLeastS()) { verifyWithOrder(inOrder, mBpfDownstream6Map).updateEntry( - makeDownstream6Key(upstreamIfindex, dst), makeDownstream6Value(dstMac)); + makeDownstream6Key(upstreamIfindex, upstreamMac, dst), + makeDownstream6Value(dstMac)); } else { verifyWithOrder(inOrder, mNetd).tetherOffloadRuleAdd(matches(upstreamIfindex, dst, dstMac)); @@ -860,10 +870,11 @@ public class IpServerTest { } private void verifyNeverTetherOffloadRuleAdd(int upstreamIfindex, - @NonNull final InetAddress dst, @NonNull final MacAddress dstMac) throws Exception { + @NonNull MacAddress upstreamMac, @NonNull final InetAddress dst, + @NonNull final MacAddress dstMac) throws Exception { if (mBpfDeps.isAtLeastS()) { verify(mBpfDownstream6Map, never()).updateEntry( - makeDownstream6Key(upstreamIfindex, dst), + makeDownstream6Key(upstreamIfindex, upstreamMac, dst), makeDownstream6Value(dstMac)); } else { verify(mNetd, never()).tetherOffloadRuleAdd(matches(upstreamIfindex, dst, dstMac)); @@ -879,10 +890,11 @@ public class IpServerTest { } private void verifyTetherOffloadRuleRemove(@Nullable InOrder inOrder, int upstreamIfindex, - @NonNull final InetAddress dst, @NonNull final MacAddress dstMac) throws Exception { + @NonNull MacAddress upstreamMac, @NonNull final InetAddress dst, + @NonNull final MacAddress dstMac) throws Exception { if (mBpfDeps.isAtLeastS()) { verifyWithOrder(inOrder, mBpfDownstream6Map).deleteEntry(makeDownstream6Key( - upstreamIfindex, dst)); + upstreamIfindex, upstreamMac, dst)); } else { // |dstMac| is not required for deleting rules. Used bacause tetherOffloadRuleRemove // uses a whole rule to be a argument. @@ -903,7 +915,8 @@ public class IpServerTest { private void verifyStartUpstreamIpv6Forwarding(@Nullable InOrder inOrder, int upstreamIfindex) throws Exception { if (!mBpfDeps.isAtLeastS()) return; - final TetherUpstream6Key key = new TetherUpstream6Key(TEST_IFACE_PARAMS.index); + final TetherUpstream6Key key = new TetherUpstream6Key(TEST_IFACE_PARAMS.index, + TEST_IFACE_PARAMS.macAddr); final Tether6Value value = new Tether6Value(upstreamIfindex, MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS, ETH_P_IPV6, NetworkStackConstants.ETHER_MTU); @@ -913,7 +926,8 @@ public class IpServerTest { private void verifyStopUpstreamIpv6Forwarding(@Nullable InOrder inOrder) throws Exception { if (!mBpfDeps.isAtLeastS()) return; - final TetherUpstream6Key key = new TetherUpstream6Key(TEST_IFACE_PARAMS.index); + final TetherUpstream6Key key = new TetherUpstream6Key(TEST_IFACE_PARAMS.index, + TEST_IFACE_PARAMS.macAddr); verifyWithOrder(inOrder, mBpfUpstream6Map).deleteEntry(key); } @@ -983,14 +997,16 @@ public class IpServerTest { recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); verify(mBpfCoordinator).tetherOffloadRuleAdd( mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA)); - verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neighA, macA); + verifyTetherOffloadRuleAdd(null, + UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, 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); + verifyTetherOffloadRuleAdd(null, + UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); verifyNoUpstreamIpv6ForwardingChange(null); resetNetdBpfMapAndCoordinator(); @@ -1005,7 +1021,8 @@ public class IpServerTest { recvNewNeigh(myIfindex, neighA, NUD_FAILED, null); verify(mBpfCoordinator).tetherOffloadRuleRemove( mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macNull)); - verifyTetherOffloadRuleRemove(null, UPSTREAM_IFINDEX, neighA, macNull); + verifyTetherOffloadRuleRemove(null, + UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macNull); verifyNoUpstreamIpv6ForwardingChange(null); resetNetdBpfMapAndCoordinator(); @@ -1013,7 +1030,8 @@ public class IpServerTest { recvDelNeigh(myIfindex, neighB, NUD_STALE, macB); verify(mBpfCoordinator).tetherOffloadRuleRemove( mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macNull)); - verifyTetherOffloadRuleRemove(null, UPSTREAM_IFINDEX, neighB, macNull); + verifyTetherOffloadRuleRemove(null, + UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macNull); verifyStopUpstreamIpv6Forwarding(null); resetNetdBpfMapAndCoordinator(); @@ -1028,12 +1046,16 @@ public class IpServerTest { lp.setInterfaceName(UPSTREAM_IFACE2); dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp, -1); verify(mBpfCoordinator).tetherOffloadRuleUpdate(mIpServer, UPSTREAM_IFINDEX2); - verifyTetherOffloadRuleRemove(inOrder, UPSTREAM_IFINDEX, neighA, macA); - verifyTetherOffloadRuleRemove(inOrder, UPSTREAM_IFINDEX, neighB, macB); + verifyTetherOffloadRuleRemove(inOrder, + UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA); + verifyTetherOffloadRuleRemove(inOrder, + UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); verifyStopUpstreamIpv6Forwarding(inOrder); - verifyTetherOffloadRuleAdd(inOrder, UPSTREAM_IFINDEX2, neighA, macA); + verifyTetherOffloadRuleAdd(inOrder, + UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighA, macA); verifyStartUpstreamIpv6Forwarding(inOrder, UPSTREAM_IFINDEX2); - verifyTetherOffloadRuleAdd(inOrder, UPSTREAM_IFINDEX2, neighB, macB); + verifyTetherOffloadRuleAdd(inOrder, + UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighB, macB); verifyNoUpstreamIpv6ForwardingChange(inOrder); resetNetdBpfMapAndCoordinator(); @@ -1044,8 +1066,10 @@ public class IpServerTest { // - processMessage CMD_IPV6_TETHER_UPDATE for the IPv6 upstream is lost. // See dispatchTetherConnectionChanged. verify(mBpfCoordinator, times(2)).tetherOffloadRuleClear(mIpServer); - verifyTetherOffloadRuleRemove(null, UPSTREAM_IFINDEX2, neighA, macA); - verifyTetherOffloadRuleRemove(null, UPSTREAM_IFINDEX2, neighB, macB); + verifyTetherOffloadRuleRemove(null, + UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighA, macA); + verifyTetherOffloadRuleRemove(null, + UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighB, macB); verifyStopUpstreamIpv6Forwarding(inOrder); resetNetdBpfMapAndCoordinator(); @@ -1064,17 +1088,20 @@ public class IpServerTest { recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); verify(mBpfCoordinator).tetherOffloadRuleAdd( mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB)); - verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neighB, macB); + verifyTetherOffloadRuleAdd(null, + UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX); verify(mBpfCoordinator, never()).tetherOffloadRuleAdd( mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA)); - verifyNeverTetherOffloadRuleAdd(UPSTREAM_IFINDEX, neighA, macA); + verifyNeverTetherOffloadRuleAdd( + UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA); // If upstream IPv6 connectivity is lost, rules are removed. resetNetdBpfMapAndCoordinator(); dispatchTetherConnectionChanged(UPSTREAM_IFACE, null, 0); verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer); - verifyTetherOffloadRuleRemove(null, UPSTREAM_IFINDEX, neighB, macB); + verifyTetherOffloadRuleRemove(null, + UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); verifyStopUpstreamIpv6Forwarding(null); // When the interface goes down, rules are removed. @@ -1084,18 +1111,22 @@ public class IpServerTest { recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); verify(mBpfCoordinator).tetherOffloadRuleAdd( mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA)); - verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neighA, macA); + verifyTetherOffloadRuleAdd(null, + UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA); verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX); verify(mBpfCoordinator).tetherOffloadRuleAdd( mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB)); - verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neighB, macB); + verifyTetherOffloadRuleAdd(null, + UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); resetNetdBpfMapAndCoordinator(); mIpServer.stop(); mLooper.dispatchAll(); verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer); - verifyTetherOffloadRuleRemove(null, UPSTREAM_IFINDEX, neighA, macA); - verifyTetherOffloadRuleRemove(null, UPSTREAM_IFINDEX, neighB, macB); + verifyTetherOffloadRuleRemove(null, + UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA); + verifyTetherOffloadRuleRemove(null, + UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); verifyStopUpstreamIpv6Forwarding(null); verify(mIpNeighborMonitor).stop(); resetNetdBpfMapAndCoordinator(); @@ -1124,14 +1155,16 @@ public class IpServerTest { recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA); verify(mBpfCoordinator).tetherOffloadRuleAdd( mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neigh, macA)); - verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neigh, macA); + verifyTetherOffloadRuleAdd(null, + UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, 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); + verifyTetherOffloadRuleRemove(null, + UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neigh, macNull); verifyStopUpstreamIpv6Forwarding(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 293d0df67e..233f6dbfa7 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java @@ -23,9 +23,21 @@ import static android.net.NetworkStats.SET_DEFAULT; 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.netlink.ConntrackMessage.DYING_MASK; +import static android.net.netlink.ConntrackMessage.ESTABLISHED_MASK; +import static android.net.netlink.ConntrackMessage.Tuple; +import static android.net.netlink.ConntrackMessage.TupleIpv4; +import static android.net.netlink.ConntrackMessage.TupleProto; +import static android.net.netlink.NetlinkConstants.IPCTNL_MSG_CT_DELETE; +import static android.net.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW; 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 android.system.OsConstants.IPPROTO_TCP; +import static android.system.OsConstants.IPPROTO_UDP; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker; import static com.android.networkstack.tethering.BpfCoordinator.StatsType; import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_IFACE; @@ -43,9 +55,9 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -55,6 +67,8 @@ import static org.mockito.Mockito.when; import android.app.usage.NetworkStatsManager; import android.net.INetd; import android.net.InetAddresses; +import android.net.LinkAddress; +import android.net.LinkProperties; import android.net.MacAddress; import android.net.NetworkStats; import android.net.TetherOffloadRuleParcel; @@ -62,6 +76,8 @@ import android.net.TetherStatsParcel; import android.net.ip.ConntrackMonitor; import android.net.ip.ConntrackMonitor.ConntrackEventConsumer; import android.net.ip.IpServer; +import android.net.netlink.NetlinkConstants; +import android.net.util.InterfaceParams; import android.net.util.SharedLog; import android.os.Build; import android.os.Handler; @@ -76,6 +92,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.net.module.util.NetworkStackConstants; import com.android.net.module.util.Struct; +import com.android.networkstack.tethering.BpfCoordinator.BpfConntrackEventConsumer; +import com.android.networkstack.tethering.BpfCoordinator.ClientInfo; import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; @@ -93,6 +111,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; +import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.util.ArrayList; @@ -108,13 +127,22 @@ public class BpfCoordinatorTest { @Rule public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); - private static final int DOWNSTREAM_IFINDEX = 10; - private static final MacAddress DOWNSTREAM_MAC = MacAddress.ALL_ZEROS_ADDRESS; - private static final InetAddress NEIGH_A = InetAddresses.parseNumericAddress("2001:db8::1"); - private static final InetAddress NEIGH_B = InetAddresses.parseNumericAddress("2001:db8::2"); + private static final int UPSTREAM_IFINDEX = 1001; + private static final int DOWNSTREAM_IFINDEX = 1002; + + private static final String UPSTREAM_IFACE = "rmnet0"; + + private static final MacAddress DOWNSTREAM_MAC = MacAddress.fromString("12:34:56:78:90:ab"); private static final MacAddress MAC_A = MacAddress.fromString("00:00:00:00:00:0a"); private static final MacAddress MAC_B = MacAddress.fromString("11:22:33:00:00:0b"); + private static final InetAddress NEIGH_A = InetAddresses.parseNumericAddress("2001:db8::1"); + private static final InetAddress NEIGH_B = InetAddresses.parseNumericAddress("2001:db8::2"); + + private static final InterfaceParams UPSTREAM_IFACE_PARAMS = new InterfaceParams( + UPSTREAM_IFACE, UPSTREAM_IFINDEX, null /* macAddr, rawip */, + NetworkStackConstants.ETHER_MTU); + // The test fake BPF map class is needed because the test has no privilege to access the BPF // map. All member functions which eventually call JNI to access the real native BPF map need // to be overridden. @@ -183,6 +211,11 @@ public class BpfCoordinatorTest { // Late init since methods must be called by the thread that created this object. private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb; private BpfCoordinator.BpfTetherStatsProvider mTetherStatsProvider; + + // Late init since the object must be initialized by the BPF coordinator instance because + // it has to access the non-static function of BPF coordinator. + private BpfConntrackEventConsumer mConsumer; + private final ArgumentCaptor<ArrayList> mStringArrayCaptor = ArgumentCaptor.forClass(ArrayList.class); private final TestLooper mTestLooper = new TestLooper(); @@ -262,6 +295,8 @@ public class BpfCoordinatorTest { mTestLooper.dispatchAll(); } + // TODO: Remove unnecessary calling on R because the BPF map accessing has been moved into + // module. private void setupFunctioningNetdInterface() throws Exception { when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]); } @@ -269,6 +304,8 @@ public class BpfCoordinatorTest { @NonNull private BpfCoordinator makeBpfCoordinator() throws Exception { final BpfCoordinator coordinator = new BpfCoordinator(mDeps); + + mConsumer = coordinator.getBpfConntrackEventConsumerForTesting(); final ArgumentCaptor<BpfCoordinator.BpfTetherStatsProvider> tetherStatsProviderCaptor = ArgumentCaptor.forClass(BpfCoordinator.BpfTetherStatsProvider.class); @@ -278,6 +315,7 @@ public class BpfCoordinatorTest { assertNotNull(mTetherStatsProvider); mTetherStatsProviderCb = new TestableNetworkStatsProviderCbBinder(); mTetherStatsProvider.setProviderCallbackBinder(mTetherStatsProviderCb); + return coordinator; } @@ -383,19 +421,20 @@ public class BpfCoordinatorTest { } private void verifyStartUpstreamIpv6Forwarding(@Nullable InOrder inOrder, int downstreamIfIndex, - int upstreamIfindex) throws Exception { + MacAddress downstreamMac, int upstreamIfindex) throws Exception { if (!mDeps.isAtLeastS()) return; - final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfIndex); + final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfIndex, downstreamMac); 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) + private void verifyStopUpstreamIpv6Forwarding(@Nullable InOrder inOrder, int downstreamIfIndex, + MacAddress downstreamMac) throws Exception { if (!mDeps.isAtLeastS()) return; - final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfIndex); + final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfIndex, downstreamMac); verifyWithOrder(inOrder, mBpfUpstream6Map).deleteEntry(key); } @@ -465,7 +504,7 @@ public class BpfCoordinatorTest { } } - private void verifyNeverTetherOffloadSetInterfaceQuota(@Nullable InOrder inOrder) + private void verifyNeverTetherOffloadSetInterfaceQuota(@NonNull InOrder inOrder) throws Exception { if (mDeps.isAtLeastS()) { inOrder.verify(mBpfStatsMap, never()).getValue(any()); @@ -476,7 +515,7 @@ public class BpfCoordinatorTest { } } - private void verifyTetherOffloadGetAndClearStats(@Nullable InOrder inOrder, int ifIndex) + private void verifyTetherOffloadGetAndClearStats(@NonNull InOrder inOrder, int ifIndex) throws Exception { if (mDeps.isAtLeastS()) { inOrder.verify(mBpfStatsMap).getValue(new TetherStatsKey(ifIndex)); @@ -732,9 +771,10 @@ public class BpfCoordinatorTest { final TetherDownstream6Key key = rule.makeTetherDownstream6Key(); assertEquals(key.iif, (long) mobileIfIndex); + assertEquals(key.dstMac, MacAddress.ALL_ZEROS_ADDRESS); // rawip upstream assertTrue(Arrays.equals(key.neigh6, NEIGH_A.getAddress())); - // iif (4) + neigh6 (16) = 20. - assertEquals(20, key.writeToBytes().length); + // iif (4) + dstMac(6) + padding(2) + neigh6 (16) = 28. + assertEquals(28, key.writeToBytes().length); } @Test @@ -797,7 +837,7 @@ public class BpfCoordinatorTest { // TODO: Test the case in which the rules are changed from different IpServer objects. @Test - public void testSetDataLimitOnRuleChange() throws Exception { + public void testSetDataLimitOnRule6Change() throws Exception { setupFunctioningNetdInterface(); final BpfCoordinator coordinator = makeBpfCoordinator(); @@ -875,7 +915,7 @@ public class BpfCoordinatorTest { verifyTetherOffloadRuleAdd(inOrder, ethernetRuleA); verifyTetherOffloadSetInterfaceQuota(inOrder, ethIfIndex, QUOTA_UNLIMITED, true /* isInit */); - verifyStartUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, ethIfIndex); + verifyStartUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC, ethIfIndex); coordinator.tetherOffloadRuleAdd(mIpServer, ethernetRuleB); verifyTetherOffloadRuleAdd(inOrder, ethernetRuleB); @@ -892,12 +932,13 @@ public class BpfCoordinatorTest { coordinator.tetherOffloadRuleUpdate(mIpServer, mobileIfIndex); verifyTetherOffloadRuleRemove(inOrder, ethernetRuleA); verifyTetherOffloadRuleRemove(inOrder, ethernetRuleB); - verifyStopUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX); + verifyStopUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC); verifyTetherOffloadGetAndClearStats(inOrder, ethIfIndex); verifyTetherOffloadRuleAdd(inOrder, mobileRuleA); verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED, true /* isInit */); - verifyStartUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, mobileIfIndex); + verifyStartUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC, + mobileIfIndex); verifyTetherOffloadRuleAdd(inOrder, mobileRuleB); // [3] Clear all rules for a given IpServer. @@ -906,7 +947,7 @@ public class BpfCoordinatorTest { coordinator.tetherOffloadRuleClear(mIpServer); verifyTetherOffloadRuleRemove(inOrder, mobileRuleA); verifyTetherOffloadRuleRemove(inOrder, mobileRuleB); - verifyStopUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX); + verifyStopUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC); verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex); // [4] Force pushing stats update to verify that the last diff of stats is reported on all @@ -1216,4 +1257,192 @@ public class BpfCoordinatorTest { coordinator.stopMonitoring(mIpServer); verify(mConntrackMonitor).stop(); } + + // Test network topology: + // + // public network (rawip) private network + // | UE | + // +------------+ V +------------+------------+ V +------------+ + // | Sever +---------+ Upstream | Downstream +---------+ Client | + // +------------+ +------------+------------+ +------------+ + // remote ip public ip private ip + // 140.112.8.116:443 100.81.179.1:62449 192.168.80.12:62449 + // + private static final Inet4Address REMOTE_ADDR = + (Inet4Address) InetAddresses.parseNumericAddress("140.112.8.116"); + private static final Inet4Address PUBLIC_ADDR = + (Inet4Address) InetAddresses.parseNumericAddress("100.81.179.1"); + private static final Inet4Address PRIVATE_ADDR = + (Inet4Address) InetAddresses.parseNumericAddress("192.168.80.12"); + + // IPv4-mapped IPv6 addresses + // Remote addrress ::ffff:140.112.8.116 + // Public addrress ::ffff:100.81.179.1 + // Private addrress ::ffff:192.168.80.12 + private static final byte[] REMOTE_ADDR_V4MAPPED_BYTES = new byte[] { + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff, + (byte) 0x8c, (byte) 0x70, (byte) 0x08, (byte) 0x74 }; + private static final byte[] PUBLIC_ADDR_V4MAPPED_BYTES = new byte[] { + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff, + (byte) 0x64, (byte) 0x51, (byte) 0xb3, (byte) 0x01 }; + private static final byte[] PRIVATE_ADDR_V4MAPPED_BYTES = new byte[] { + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff, + (byte) 0xc0, (byte) 0xa8, (byte) 0x50, (byte) 0x0c }; + + // Generally, public port and private port are the same in the NAT conntrack message. + // TODO: consider using different private port and public port for testing. + private static final short REMOTE_PORT = (short) 443; + private static final short PUBLIC_PORT = (short) 62449; + private static final short PRIVATE_PORT = (short) 62449; + + @NonNull + private Tether4Key makeUpstream4Key(int proto) { + if (proto != IPPROTO_TCP && proto != IPPROTO_UDP) { + fail("Not support protocol " + proto); + } + return new Tether4Key(DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC, (short) proto, + PRIVATE_ADDR.getAddress(), REMOTE_ADDR.getAddress(), PRIVATE_PORT, REMOTE_PORT); + } + + @NonNull + private Tether4Key makeDownstream4Key(int proto) { + if (proto != IPPROTO_TCP && proto != IPPROTO_UDP) { + fail("Not support protocol " + proto); + } + return new Tether4Key(UPSTREAM_IFINDEX, + MacAddress.ALL_ZEROS_ADDRESS /* dstMac (rawip) */, (short) proto, + REMOTE_ADDR.getAddress(), PUBLIC_ADDR.getAddress(), REMOTE_PORT, PUBLIC_PORT); + } + + @NonNull + private Tether4Value makeUpstream4Value() { + return new Tether4Value(UPSTREAM_IFINDEX, + MacAddress.ALL_ZEROS_ADDRESS /* ethDstMac (rawip) */, + MacAddress.ALL_ZEROS_ADDRESS /* ethSrcMac (rawip) */, ETH_P_IP, + NetworkStackConstants.ETHER_MTU, PUBLIC_ADDR_V4MAPPED_BYTES, + REMOTE_ADDR_V4MAPPED_BYTES, PUBLIC_PORT, REMOTE_PORT, 0 /* lastUsed */); + } + + @NonNull + private Tether4Value makeDownstream4Value() { + return new Tether4Value(DOWNSTREAM_IFINDEX, MAC_A /* client mac */, DOWNSTREAM_MAC, + ETH_P_IP, NetworkStackConstants.ETHER_MTU, REMOTE_ADDR_V4MAPPED_BYTES, + PRIVATE_ADDR_V4MAPPED_BYTES, REMOTE_PORT, PRIVATE_PORT, 0 /* lastUsed */); + } + + @NonNull + private ConntrackEvent makeTestConntrackEvent(short msgType, int proto) { + if (msgType != IPCTNL_MSG_CT_NEW && msgType != IPCTNL_MSG_CT_DELETE) { + fail("Not support message type " + msgType); + } + if (proto != IPPROTO_TCP && proto != IPPROTO_UDP) { + fail("Not support protocol " + proto); + } + + final int status = (msgType == IPCTNL_MSG_CT_NEW) ? ESTABLISHED_MASK : DYING_MASK; + final int timeoutSec = (msgType == IPCTNL_MSG_CT_NEW) ? 100 /* nonzero, new */ + : 0 /* unused, delete */; + return new ConntrackEvent( + (short) (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8 | msgType), + new Tuple(new TupleIpv4(PRIVATE_ADDR, REMOTE_ADDR), + new TupleProto((byte) proto, PRIVATE_PORT, REMOTE_PORT)), + new Tuple(new TupleIpv4(REMOTE_ADDR, PUBLIC_ADDR), + new TupleProto((byte) proto, REMOTE_PORT, PUBLIC_PORT)), + status, + timeoutSec); + } + + private void setUpstreamInformationTo(final BpfCoordinator coordinator) { + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(UPSTREAM_IFACE); + lp.addLinkAddress(new LinkAddress(PUBLIC_ADDR, 32 /* prefix length */)); + coordinator.addUpstreamIfindexToMap(lp); + } + + private void setDownstreamAndClientInformationTo(final BpfCoordinator coordinator) { + final ClientInfo clientInfo = new ClientInfo(DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC, + PRIVATE_ADDR, MAC_A /* client mac */); + coordinator.tetherOffloadClientAdd(mIpServer, clientInfo); + } + + // TODO: Test the IPv4 and IPv6 exist concurrently. + // TODO: Test the IPv4 rule delete failed. + @Test + @IgnoreUpTo(Build.VERSION_CODES.R) + public void testSetDataLimitOnRule4Change() throws Exception { + final BpfCoordinator coordinator = makeBpfCoordinator(); + coordinator.startPolling(); + + // Needed because tetherOffloadRuleRemove of api31.BpfCoordinatorShimImpl only decreases + // the count while the entry is deleted. In the other words, deleteEntry returns true. + doReturn(true).when(mBpfDownstream4Map).deleteEntry(any()); + + // Needed because BpfCoordinator#addUpstreamIfindexToMap queries interface parameter for + // interface index. + doReturn(UPSTREAM_IFACE_PARAMS).when(mDeps).getInterfaceParams(UPSTREAM_IFACE); + + coordinator.addUpstreamNameToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE); + setUpstreamInformationTo(coordinator); + setDownstreamAndClientInformationTo(coordinator); + + // 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, mBpfUpstream4Map, mBpfDownstream4Map, mBpfLimitMap, + mBpfStatsMap); + mTetherStatsProvider.onSetLimit(UPSTREAM_IFACE, limit); + waitForIdle(); + verifyNeverTetherOffloadSetInterfaceQuota(inOrder); + + // Build TCP and UDP rules for testing. Note that the values of {TCP, UDP} are the same + // because the protocol is not an element of the value. Consider using different address + // or port to make them different for better testing. + // TODO: Make the values of {TCP, UDP} rules different. + final Tether4Key expectedUpstream4KeyTcp = makeUpstream4Key(IPPROTO_TCP); + final Tether4Key expectedDownstream4KeyTcp = makeDownstream4Key(IPPROTO_TCP); + final Tether4Value expectedUpstream4ValueTcp = makeUpstream4Value(); + final Tether4Value expectedDownstream4ValueTcp = makeDownstream4Value(); + + final Tether4Key expectedUpstream4KeyUdp = makeUpstream4Key(IPPROTO_UDP); + final Tether4Key expectedDownstream4KeyUdp = makeDownstream4Key(IPPROTO_UDP); + final Tether4Value expectedUpstream4ValueUdp = makeUpstream4Value(); + final Tether4Value expectedDownstream4ValueUdp = makeDownstream4Value(); + + // [1] Adding the first rule on current upstream immediately sends the quota. + mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, IPPROTO_TCP)); + verifyTetherOffloadSetInterfaceQuota(inOrder, UPSTREAM_IFINDEX, limit, true /* isInit */); + inOrder.verify(mBpfUpstream4Map) + .insertEntry(eq(expectedUpstream4KeyTcp), eq(expectedUpstream4ValueTcp)); + inOrder.verify(mBpfDownstream4Map) + .insertEntry(eq(expectedDownstream4KeyTcp), eq(expectedDownstream4ValueTcp)); + inOrder.verifyNoMoreInteractions(); + + // [2] Adding the second rule on current upstream does not send the quota. + mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, IPPROTO_UDP)); + verifyNeverTetherOffloadSetInterfaceQuota(inOrder); + inOrder.verify(mBpfUpstream4Map) + .insertEntry(eq(expectedUpstream4KeyUdp), eq(expectedUpstream4ValueUdp)); + inOrder.verify(mBpfDownstream4Map) + .insertEntry(eq(expectedDownstream4KeyUdp), eq(expectedDownstream4ValueUdp)); + inOrder.verifyNoMoreInteractions(); + + // [3] Removing the second rule on current upstream does not send the quota. + mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_DELETE, IPPROTO_UDP)); + verifyNeverTetherOffloadSetInterfaceQuota(inOrder); + inOrder.verify(mBpfUpstream4Map).deleteEntry(eq(expectedUpstream4KeyUdp)); + inOrder.verify(mBpfDownstream4Map).deleteEntry(eq(expectedDownstream4KeyUdp)); + inOrder.verifyNoMoreInteractions(); + + // [4] Removing the last rule on current upstream immediately sends the cleanup stuff. + updateStatsEntryForTetherOffloadGetAndClearStats( + buildTestTetherStatsParcel(UPSTREAM_IFINDEX, 0, 0, 0, 0)); + mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_DELETE, IPPROTO_TCP)); + inOrder.verify(mBpfUpstream4Map).deleteEntry(eq(expectedUpstream4KeyTcp)); + inOrder.verify(mBpfDownstream4Map).deleteEntry(eq(expectedDownstream4KeyTcp)); + verifyTetherOffloadGetAndClearStats(inOrder, UPSTREAM_IFINDEX); + inOrder.verifyNoMoreInteractions(); + } } diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java index a7287a2d5c..d045bf169c 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java @@ -32,6 +32,8 @@ import android.os.Handler; import android.os.UserHandle; import android.util.ArrayMap; +import androidx.annotation.Nullable; + import java.util.Map; import java.util.Objects; @@ -58,6 +60,9 @@ import java.util.Objects; * that state changes), this may become less important or unnecessary. */ public class TestConnectivityManager extends ConnectivityManager { + public static final boolean BROADCAST_FIRST = false; + public static final boolean CALLBACKS_FIRST = true; + final Map<NetworkCallback, NetworkRequestInfo> mAllCallbacks = new ArrayMap<>(); final Map<NetworkCallback, NetworkRequestInfo> mTrackingDefault = new ArrayMap<>(); final Map<NetworkCallback, NetworkRequestInfo> mListening = new ArrayMap<>(); @@ -151,14 +156,29 @@ public class TestConnectivityManager extends ConnectivityManager { } } - void makeDefaultNetwork(TestNetworkAgent agent) { + void makeDefaultNetwork(TestNetworkAgent agent, boolean order, @Nullable Runnable inBetween) { if (Objects.equals(mDefaultNetwork, agent)) return; final TestNetworkAgent formerDefault = mDefaultNetwork; mDefaultNetwork = agent; - sendDefaultNetworkCallbacks(formerDefault, mDefaultNetwork); - sendDefaultNetworkBroadcasts(formerDefault, mDefaultNetwork); + if (order == CALLBACKS_FIRST) { + sendDefaultNetworkCallbacks(formerDefault, mDefaultNetwork); + if (inBetween != null) inBetween.run(); + sendDefaultNetworkBroadcasts(formerDefault, mDefaultNetwork); + } else { + sendDefaultNetworkBroadcasts(formerDefault, mDefaultNetwork); + if (inBetween != null) inBetween.run(); + sendDefaultNetworkCallbacks(formerDefault, mDefaultNetwork); + } + } + + void makeDefaultNetwork(TestNetworkAgent agent, boolean order) { + makeDefaultNetwork(agent, order, null /* inBetween */); + } + + void makeDefaultNetwork(TestNetworkAgent agent) { + makeDefaultNetwork(agent, BROADCAST_FIRST, null /* inBetween */); } @Override @@ -308,6 +328,7 @@ public class TestConnectivityManager extends ConnectivityManager { networkId, copy(networkCapabilities))); nri.handler.post(() -> cb.onLinkPropertiesChanged(networkId, copy(linkProperties))); } + // mTrackingDefault will be updated if/when the caller calls makeDefaultNetwork } public void fakeDisconnect() { @@ -320,6 +341,7 @@ public class TestConnectivityManager extends ConnectivityManager { for (NetworkCallback cb : cm.mListening.keySet()) { cb.onLost(networkId); } + // mTrackingDefault will be updated if/when the caller calls makeDefaultNetwork } public void sendLinkProperties() { diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java index a7d61bd341..776298c4e1 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -59,6 +59,8 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH; import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH; +import static com.android.networkstack.tethering.TestConnectivityManager.BROADCAST_FIRST; +import static com.android.networkstack.tethering.TestConnectivityManager.CALLBACKS_FIRST; import static com.android.networkstack.tethering.Tethering.UserRestrictionActionListener; import static com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE; import static com.android.networkstack.tethering.UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES; @@ -332,13 +334,14 @@ public class TetheringTest { assertTrue("Non-mocked interface " + ifName, ifName.equals(TEST_USB_IFNAME) || ifName.equals(TEST_WLAN_IFNAME) + || ifName.equals(TEST_WIFI_IFNAME) || ifName.equals(TEST_MOBILE_IFNAME) || ifName.equals(TEST_P2P_IFNAME) || ifName.equals(TEST_NCM_IFNAME) || ifName.equals(TEST_ETH_IFNAME)); final String[] ifaces = new String[] { - TEST_USB_IFNAME, TEST_WLAN_IFNAME, TEST_MOBILE_IFNAME, TEST_P2P_IFNAME, - TEST_NCM_IFNAME, TEST_ETH_IFNAME}; + TEST_USB_IFNAME, TEST_WLAN_IFNAME, TEST_WIFI_IFNAME, TEST_MOBILE_IFNAME, + TEST_P2P_IFNAME, TEST_NCM_IFNAME, TEST_ETH_IFNAME}; return new InterfaceParams(ifName, ArrayUtils.indexOf(ifaces, ifName) + IFINDEX_OFFSET, MacAddress.ALL_ZEROS_ADDRESS); } @@ -1107,22 +1110,38 @@ public class TetheringTest { // Pretend cellular connected and expect the upstream to be set. TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState()); mobile.fakeConnect(); - mCm.makeDefaultNetwork(mobile); + mCm.makeDefaultNetwork(mobile, BROADCAST_FIRST); mLooper.dispatchAll(); inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId); // Switch upstreams a few times. - // TODO: there may be a race where if the effects of the CONNECTIVITY_ACTION happen before - // UpstreamNetworkMonitor gets onCapabilitiesChanged on CALLBACK_DEFAULT_INTERNET, the - // upstream does not change. Extend TestConnectivityManager to simulate this condition and - // write a test for this. TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState()); wifi.fakeConnect(); - mCm.makeDefaultNetwork(wifi); + mCm.makeDefaultNetwork(wifi, BROADCAST_FIRST); mLooper.dispatchAll(); inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(wifi.networkId); - mCm.makeDefaultNetwork(mobile); + // This code has historically been racy, so test different orderings of CONNECTIVITY_ACTION + // broadcasts and callbacks, and add mLooper.dispatchAll() calls between the two. + final Runnable doDispatchAll = () -> mLooper.dispatchAll(); + + mCm.makeDefaultNetwork(mobile, BROADCAST_FIRST, doDispatchAll); + mLooper.dispatchAll(); + inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId); + + mCm.makeDefaultNetwork(wifi, BROADCAST_FIRST, doDispatchAll); + mLooper.dispatchAll(); + inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(wifi.networkId); + + mCm.makeDefaultNetwork(mobile, CALLBACKS_FIRST); + mLooper.dispatchAll(); + inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId); + + mCm.makeDefaultNetwork(wifi, CALLBACKS_FIRST); + mLooper.dispatchAll(); + inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(wifi.networkId); + + mCm.makeDefaultNetwork(mobile, CALLBACKS_FIRST, doDispatchAll); mLooper.dispatchAll(); inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId); @@ -1134,14 +1153,15 @@ public class TetheringTest { // Lose and regain upstream. assertTrue(mUpstreamNetworkMonitor.getCurrentPreferredUpstream().linkProperties .hasIPv4Address()); + mCm.makeDefaultNetwork(null, BROADCAST_FIRST, doDispatchAll); + mLooper.dispatchAll(); mobile.fakeDisconnect(); - mCm.makeDefaultNetwork(null); mLooper.dispatchAll(); inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(null); mobile = new TestNetworkAgent(mCm, buildMobile464xlatUpstreamState()); mobile.fakeConnect(); - mCm.makeDefaultNetwork(mobile); + mCm.makeDefaultNetwork(mobile, BROADCAST_FIRST, doDispatchAll); mLooper.dispatchAll(); inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId); @@ -1149,10 +1169,21 @@ public class TetheringTest { // mobile upstream, even though the netId is (unrealistically) the same. assertFalse(mUpstreamNetworkMonitor.getCurrentPreferredUpstream().linkProperties .hasIPv4Address()); + + // Lose and regain upstream again. + mCm.makeDefaultNetwork(null, CALLBACKS_FIRST, doDispatchAll); mobile.fakeDisconnect(); - mCm.makeDefaultNetwork(null); mLooper.dispatchAll(); inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(null); + + mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState()); + mobile.fakeConnect(); + mCm.makeDefaultNetwork(mobile, CALLBACKS_FIRST, doDispatchAll); + mLooper.dispatchAll(); + inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId); + + assertTrue(mUpstreamNetworkMonitor.getCurrentPreferredUpstream().linkProperties + .hasIPv4Address()); } private void runNcmTethering() { diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl b/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl index 5aafdf06cb..f523745caf 100644 --- a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl +++ b/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl @@ -24,6 +24,6 @@ interface IMyService { String checkNetworkStatus(); String getRestrictBackgroundStatus(); void sendNotification(int notificationId, String notificationType); - void registerNetworkCallback(in INetworkCallback cb); + void registerNetworkCallback(in NetworkRequest request, in INetworkCallback cb); void unregisterNetworkCallback(); } diff --git a/tests/cts/hostside/app/Android.bp b/tests/cts/hostside/app/Android.bp index 50fda6de13..a9686de9bc 100644 --- a/tests/cts/hostside/app/Android.bp +++ b/tests/cts/hostside/app/Android.bp @@ -27,6 +27,7 @@ android_test_helper_app { "androidx.test.rules", "androidx.test.ext.junit", "compatibility-device-util-axt", + "cts-net-utils", "ctstestrunner-axt", "ub-uiautomator", "CtsHostsideNetworkTestsAidl", diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java index 48923b9acb..1afbfb0ab1 100644 --- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java +++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java @@ -47,6 +47,7 @@ import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.NetworkInfo.DetailedState; import android.net.NetworkInfo.State; +import android.net.NetworkRequest; import android.os.BatteryManager; import android.os.Binder; import android.os.Bundle; @@ -713,8 +714,10 @@ public abstract class AbstractRestrictBackgroundNetworkTestCase { fail("app2 receiver is not ready"); } - protected void registerNetworkCallback(INetworkCallback cb) throws Exception { - mServiceClient.registerNetworkCallback(cb); + protected void registerNetworkCallback(final NetworkRequest request, INetworkCallback cb) + throws Exception { + Log.i(TAG, "Registering network callback for request: " + request); + mServiceClient.registerNetworkCallback(request, cb); } protected void unregisterNetworkCallback() throws Exception { diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyActivity.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyActivity.java index 0d0bc58504..55eec1190d 100644 --- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyActivity.java +++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyActivity.java @@ -17,6 +17,7 @@ package com.android.cts.net.hostside; import android.app.Activity; +import android.app.KeyguardManager; import android.content.Intent; import android.os.Bundle; import android.view.WindowManager; @@ -34,6 +35,11 @@ public class MyActivity extends Activity { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); + + // Dismiss the keyguard so that the tests can click on the VPN confirmation dialog. + // FLAG_DISMISS_KEYGUARD is not sufficient to do this because as soon as the dialog appears, + // this activity goes into the background and the keyguard reappears. + getSystemService(KeyguardManager.class).requestDismissKeyguard(this, null /* callback */); } @Override diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java index 6546e26ba7..c37e8d51e3 100644 --- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java +++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java @@ -20,12 +20,11 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.net.NetworkRequest; import android.os.ConditionVariable; import android.os.IBinder; import android.os.RemoteException; -import com.android.cts.net.hostside.IMyService; - public class MyServiceClient { private static final int TIMEOUT_MS = 5000; private static final String PACKAGE = MyServiceClient.class.getPackage().getName(); @@ -93,12 +92,14 @@ public class MyServiceClient { return mService.getRestrictBackgroundStatus(); } - public void sendNotification(int notificationId, String notificationType) throws RemoteException { + public void sendNotification(int notificationId, String notificationType) + throws RemoteException { mService.sendNotification(notificationId, notificationType); } - public void registerNetworkCallback(INetworkCallback cb) throws RemoteException { - mService.registerNetworkCallback(cb); + public void registerNetworkCallback(final NetworkRequest request, INetworkCallback cb) + throws RemoteException { + mService.registerNetworkCallback(request, cb); } public void unregisterNetworkCallback() throws RemoteException { 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 955317bbf6..36e2ffea08 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 @@ -19,6 +19,7 @@ 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.getActiveNetworkCapabilities; import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground; import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE; import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE; @@ -29,6 +30,7 @@ import static org.junit.Assume.assumeTrue; import android.net.Network; import android.net.NetworkCapabilities; +import android.net.NetworkRequest; import android.util.Log; import org.junit.After; @@ -195,11 +197,16 @@ public class NetworkCallbackTest extends AbstractRestrictBackgroundNetworkTestCa setBatterySaverMode(false); setRestrictBackground(false); + // Get transports of the active network, this has to be done before changing meteredness, + // since wifi will be disconnected when changing from non-metered to metered. + final NetworkCapabilities networkCapabilities = getActiveNetworkCapabilities(); + // Mark network as metered. mMeterednessConfiguration.configureNetworkMeteredness(true); // Register callback - registerNetworkCallback((INetworkCallback.Stub) mTestNetworkCallback); + registerNetworkCallback(new NetworkRequest.Builder() + .setCapabilities(networkCapabilities).build(), mTestNetworkCallback); // Wait for onAvailable() callback to ensure network is available before the test // and store the default network. mNetwork = mTestNetworkCallback.expectAvailableCallbackAndGetNetwork(); 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 0a134080ff..e62d5571ff 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 @@ -201,7 +201,7 @@ public class NetworkPolicyTestUtils { } } - private static NetworkCapabilities getActiveNetworkCapabilities() { + static NetworkCapabilities getActiveNetworkCapabilities() { final Network activeNetwork = getConnectivityManager().getActiveNetwork(); assertNotNull("No active network available", activeNetwork); return getConnectivityManager().getNetworkCapabilities(activeNetwork); 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 9b437e64a8..c0600e761f 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 @@ -61,6 +61,7 @@ import android.os.Looper; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.SystemProperties; +import android.os.UserHandle; import android.provider.Settings; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject; @@ -76,6 +77,7 @@ import android.util.Log; import com.android.compatibility.common.util.BlockingBroadcastReceiver; import com.android.modules.utils.build.SdkLevel; +import com.android.testutils.TestableNetworkCallback; import java.io.Closeable; import java.io.FileDescriptor; @@ -92,6 +94,7 @@ import java.net.Socket; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.Random; import java.util.concurrent.CompletableFuture; @@ -698,34 +701,6 @@ public class VpnTest extends InstrumentationTestCase { 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. @@ -741,13 +716,24 @@ public class VpnTest extends InstrumentationTestCase { getInstrumentation().getTargetContext(), MyVpnService.ACTION_ESTABLISHED); receiver.register(); - // Expect the system default network not to change. - final NeverChangeNetworkCallback neverChangeCallback = new NeverChangeNetworkCallback(); + // Test the behaviour of a variety of types of network callbacks. final Network defaultNetwork = mCM.getActiveNetwork(); + final TestableNetworkCallback systemDefaultCallback = new TestableNetworkCallback(); + final TestableNetworkCallback otherUidCallback = new TestableNetworkCallback(); + final TestableNetworkCallback myUidCallback = new TestableNetworkCallback(); if (SdkLevel.isAtLeastS()) { - runWithShellPermissionIdentity(() -> - mCM.registerSystemDefaultNetworkCallback(neverChangeCallback, - new Handler(Looper.getMainLooper())), NETWORK_SETTINGS); + final int otherUid = UserHandle.getUid(UserHandle.of(5), Process.FIRST_APPLICATION_UID); + final Handler h = new Handler(Looper.getMainLooper()); + runWithShellPermissionIdentity(() -> { + mCM.registerSystemDefaultNetworkCallback(systemDefaultCallback, h); + mCM.registerDefaultNetworkCallbackAsUid(otherUid, otherUidCallback, h); + mCM.registerDefaultNetworkCallbackAsUid(Process.myUid(), myUidCallback, h); + }, NETWORK_SETTINGS); + for (TestableNetworkCallback callback : + List.of(systemDefaultCallback, otherUidCallback, myUidCallback)) { + callback.expectAvailableCallbacks(defaultNetwork, false /* suspended */, + true /* validated */, false /* blocked */, TIMEOUT_MS); + } } FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS); @@ -767,20 +753,24 @@ public class VpnTest extends InstrumentationTestCase { checkTrafficOnVpn(); - maybeExpectVpnTransportInfo(mCM.getActiveNetwork()); + final Network vpnNetwork = mCM.getActiveNetwork(); + myUidCallback.expectAvailableThenValidatedCallbacks(vpnNetwork, TIMEOUT_MS); + assertEquals(vpnNetwork, mCM.getActiveNetwork()); + assertNotEqual(defaultNetwork, vpnNetwork); + maybeExpectVpnTransportInfo(vpnNetwork); - assertNotEqual(defaultNetwork, mCM.getActiveNetwork()); if (SdkLevel.isAtLeastS()) { // 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()); - neverChangeCallback.assertNeverChanged(); - runWithShellPermissionIdentity( - () -> mCM.unregisterNetworkCallback(neverChangeCallback), - NETWORK_SETTINGS); + // though the app's default network changed. Also check that otherUidCallback saw no + // network changes, because otherUid is in a different user and not subject to the VPN. + // 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 could cause the default network to switch (e.g., from wifi to cellular). + systemDefaultCallback.assertNoCallback(); + otherUidCallback.assertNoCallback(); + mCM.unregisterNetworkCallback(systemDefaultCallback); + mCM.unregisterNetworkCallback(otherUidCallback); + mCM.unregisterNetworkCallback(myUidCallback); } checkStrictModePrivateDns(); 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 1c9ff05a5e..8a5e00fdaf 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 @@ -90,7 +90,7 @@ public class MyService extends Service { } @Override - public void registerNetworkCallback(INetworkCallback cb) { + public void registerNetworkCallback(final NetworkRequest request, INetworkCallback cb) { if (mNetworkCallback != null) { Log.d(TAG, "unregister previous network callback: " + mNetworkCallback); unregisterNetworkCallback(); @@ -138,7 +138,7 @@ public class MyService extends Service { } } }; - mCm.registerNetworkCallback(makeNetworkRequest(), mNetworkCallback); + mCm.registerNetworkCallback(request, mNetworkCallback); try { cb.asBinder().linkToDeath(() -> unregisterNetworkCallback(), 0); } catch (RemoteException e) { @@ -156,12 +156,6 @@ public class MyService extends Service { } }; - private NetworkRequest makeNetworkRequest() { - return new NetworkRequest.Builder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .build(); - } - @Override public IBinder onBind(Intent intent) { return mBinder; diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java index bfab497316..18f05888b4 100644 --- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java +++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java @@ -111,6 +111,7 @@ import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.MessageQueue; +import android.os.Process; import android.os.SystemClock; import android.os.SystemProperties; import android.os.VintfRuntimeInfo; @@ -587,12 +588,14 @@ public class ConnectivityManagerTest { final TestNetworkCallback defaultTrackingCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(defaultTrackingCallback); - final TestNetworkCallback systemDefaultTrackingCallback = new TestNetworkCallback(); + final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback(); + final TestNetworkCallback perUidCallback = new TestNetworkCallback(); + final Handler h = new Handler(Looper.getMainLooper()); if (shouldTestSApis()) { - runWithShellPermissionIdentity(() -> - mCmShim.registerSystemDefaultNetworkCallback(systemDefaultTrackingCallback, - new Handler(Looper.getMainLooper())), - NETWORK_SETTINGS); + runWithShellPermissionIdentity(() -> { + mCmShim.registerSystemDefaultNetworkCallback(systemDefaultCallback, h); + mCmShim.registerDefaultNetworkCallbackAsUid(Process.myUid(), perUidCallback, h); + }, NETWORK_SETTINGS); } Network wifiNetwork = null; @@ -607,22 +610,27 @@ public class ConnectivityManagerTest { assertNotNull("Did not receive onAvailable for TRANSPORT_WIFI request", wifiNetwork); + final Network defaultNetwork = defaultTrackingCallback.waitForAvailable(); assertNotNull("Did not receive onAvailable on default network callback", - defaultTrackingCallback.waitForAvailable()); + defaultNetwork); if (shouldTestSApis()) { assertNotNull("Did not receive onAvailable on system default network callback", - systemDefaultTrackingCallback.waitForAvailable()); + systemDefaultCallback.waitForAvailable()); + final Network perUidNetwork = perUidCallback.waitForAvailable(); + assertNotNull("Did not receive onAvailable on per-UID default network callback", + perUidNetwork); + assertEquals(defaultNetwork, perUidNetwork); } + } catch (InterruptedException e) { fail("Broadcast receiver or NetworkCallback wait was interrupted."); } finally { mCm.unregisterNetworkCallback(callback); mCm.unregisterNetworkCallback(defaultTrackingCallback); if (shouldTestSApis()) { - runWithShellPermissionIdentity( - () -> mCm.unregisterNetworkCallback(systemDefaultTrackingCallback), - NETWORK_SETTINGS); + mCm.unregisterNetworkCallback(systemDefaultCallback); + mCm.unregisterNetworkCallback(perUidCallback); } } } @@ -1636,6 +1644,62 @@ public class ConnectivityManagerTest { } /** + * Verifies that apps are forbidden from getting ssid information from + * {@Code NetworkCapabilities} if they do not hold NETWORK_SETTINGS permission. + * See b/161370134. + */ + @AppModeFull(reason = "Cannot get WifiManager in instant app mode") + @Test + public void testSsidInNetworkCapabilities() throws Exception { + assumeTrue("testSsidInNetworkCapabilities cannot execute unless device supports WiFi", + mPackageManager.hasSystemFeature(FEATURE_WIFI)); + + final Network network = mCtsNetUtils.ensureWifiConnected(); + final String ssid = unquoteSSID(mWifiManager.getConnectionInfo().getSSID()); + assertNotNull("Ssid getting from WiifManager is null", ssid); + // This package should have no NETWORK_SETTINGS permission. Verify that no ssid is contained + // in the NetworkCapabilities. + verifySsidFromQueriedNetworkCapabilities(network, ssid, false /* hasSsid */); + verifySsidFromCallbackNetworkCapabilities(ssid, false /* hasSsid */); + // Adopt shell permission to allow to get ssid information. + runWithShellPermissionIdentity(() -> { + verifySsidFromQueriedNetworkCapabilities(network, ssid, true /* hasSsid */); + verifySsidFromCallbackNetworkCapabilities(ssid, true /* hasSsid */); + }); + } + + private void verifySsidFromQueriedNetworkCapabilities(@NonNull Network network, + @NonNull String ssid, boolean hasSsid) throws Exception { + // Verify if ssid is contained in NetworkCapabilities queried from ConnectivityManager. + final NetworkCapabilities nc = mCm.getNetworkCapabilities(network); + assertNotNull("NetworkCapabilities of the network is null", nc); + assertEquals(hasSsid, Pattern.compile(ssid).matcher(nc.toString()).find()); + } + + private void verifySsidFromCallbackNetworkCapabilities(@NonNull String ssid, boolean hasSsid) + throws Exception { + final CompletableFuture<NetworkCapabilities> foundNc = new CompletableFuture(); + final NetworkCallback callback = new NetworkCallback() { + @Override + public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { + foundNc.complete(nc); + } + }; + try { + mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback); + // Registering a callback here guarantees onCapabilitiesChanged is called immediately + // because WiFi network should be connected. + final NetworkCapabilities nc = + foundNc.get(NETWORK_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS); + // Verify if ssid is contained in the NetworkCapabilities received from callback. + assertNotNull("NetworkCapabilities of the network is null", nc); + assertEquals(hasSsid, Pattern.compile(ssid).matcher(nc.toString()).find()); + } finally { + mCm.unregisterNetworkCallback(callback); + } + } + + /** * Verify background request can only be requested when acquiring * {@link android.Manifest.permission.NETWORK_SETTINGS}. */ diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt index ef529f8289..f53a2a80b3 100644 --- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt +++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt @@ -55,6 +55,8 @@ 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 +import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnNetworkCreated +import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnNetworkDestroyed import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnNetworkUnwanted import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnRemoveKeepalivePacketFilter import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnSaveAcceptUnvalidated @@ -216,6 +218,8 @@ class NetworkAgentTest { object OnAutomaticReconnectDisabled : CallbackEntry() data class OnValidationStatus(val status: Int, val uri: Uri?) : CallbackEntry() data class OnSignalStrengthThresholdsUpdated(val thresholds: IntArray) : CallbackEntry() + object OnNetworkCreated : CallbackEntry() + object OnNetworkDestroyed : CallbackEntry() } override fun onBandwidthUpdateRequested() { @@ -269,6 +273,14 @@ class NetworkAgentTest { history.add(OnValidationStatus(status, uri)) } + override fun onNetworkCreated() { + history.add(OnNetworkCreated) + } + + override fun onNetworkDestroyed() { + history.add(OnNetworkDestroyed) + } + // Expects the initial validation event that always occurs immediately after registering // a NetworkAgent whose network does not require validation (which test networks do // not, since they lack the INTERNET capability). It always contains the default argument @@ -347,8 +359,10 @@ class NetworkAgentTest { val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS) requestNetwork(request, callback) val agent = createNetworkAgent(context, name) + agent.setTeardownDelayMs(0) agent.register() agent.markConnected() + agent.expectCallback<OnNetworkCreated>() return agent to callback } @@ -368,6 +382,7 @@ class NetworkAgentTest { assertFailsWith<IllegalStateException>("Must not be able to register an agent twice") { agent.register() } + agent.expectCallback<OnNetworkDestroyed>() } @Test @@ -547,6 +562,7 @@ class NetworkAgentTest { @Test @IgnoreUpTo(Build.VERSION_CODES.R) fun testSetUnderlyingNetworksAndVpnSpecifier() { + val mySessionId = "MySession12345" val request = NetworkRequest.Builder() .addTransportType(TRANSPORT_TEST) .addTransportType(TRANSPORT_VPN) @@ -560,7 +576,7 @@ class NetworkAgentTest { addTransportType(TRANSPORT_TEST) addTransportType(TRANSPORT_VPN) removeCapability(NET_CAPABILITY_NOT_VPN) - setTransportInfo(VpnTransportInfo(VpnManager.TYPE_VPN_SERVICE)) + setTransportInfo(VpnTransportInfo(VpnManager.TYPE_VPN_SERVICE, mySessionId)) if (SdkLevel.isAtLeastS()) { addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) } @@ -580,6 +596,8 @@ class NetworkAgentTest { assertNotNull(vpnNc) assertEquals(VpnManager.TYPE_VPN_SERVICE, (vpnNc.transportInfo as VpnTransportInfo).type) + // TODO: b/183938194 please fix the issue and enable following check. + // assertEquals(mySessionId, (vpnNc.transportInfo as VpnTransportInfo).sessionId) val testAndVpn = intArrayOf(TRANSPORT_TEST, TRANSPORT_VPN) assertTrue(hasAllTransports(vpnNc, testAndVpn)) @@ -627,7 +645,8 @@ class NetworkAgentTest { val mockContext = mock(Context::class.java) val mockCm = mock(ConnectivityManager::class.java) doReturn(mockCm).`when`(mockContext).getSystemService(Context.CONNECTIVITY_SERVICE) - createConnectedNetworkAgent(mockContext) + val agent = createNetworkAgent(mockContext) + agent.register() verify(mockCm).registerNetworkAgent(any(), argThat<NetworkInfo> { it.detailedState == NetworkInfo.DetailedState.CONNECTING }, any(LinkProperties::class.java), |