From c0aa632f96830ac6488812bfa155343dcdbe3ff8 Mon Sep 17 00:00:00 2001 From: markchien Date: Wed, 4 Nov 2020 21:52:52 +0800 Subject: [PATCH 1/6] Add tethering privileged test Create TetheringPrivilegedTests which have MAINLINE_NETWORK_STACK permission, the test can perform various network-related operations which need CAP_NET_RAW and CAP_NET_ADMIN capabilities. Bug: 145490751 Test: make TetheringPrivilegedTests Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1345361 Merged-In: I3f1d7019f1a12647b78630a412df3adf03e9e95a Change-Id: I93f8f6510e12a2a44bc576a4a801c0aac629df25 --- Tethering/tests/privileged/Android.bp | 30 +++++++++++++++++ .../tests/privileged/AndroidManifest.xml | 32 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 Tethering/tests/privileged/Android.bp create mode 100644 Tethering/tests/privileged/AndroidManifest.xml diff --git a/Tethering/tests/privileged/Android.bp b/Tethering/tests/privileged/Android.bp new file mode 100644 index 0000000000..a0fb24603a --- /dev/null +++ b/Tethering/tests/privileged/Android.bp @@ -0,0 +1,30 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +android_test { + name: "TetheringPrivilegedTests", + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + certificate: "networkstack", + platform_apis: true, + test_suites: [ + "general-tests", + "mts", + ], + compile_multilib: "both", +} diff --git a/Tethering/tests/privileged/AndroidManifest.xml b/Tethering/tests/privileged/AndroidManifest.xml new file mode 100644 index 0000000000..49eba15d13 --- /dev/null +++ b/Tethering/tests/privileged/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + From 016ee9717109d0b0df9e142cc700de5fbb3777f6 Mon Sep 17 00:00:00 2001 From: markchien Date: Wed, 4 Nov 2020 21:57:43 +0800 Subject: [PATCH 2/6] tethering: DAD Proxy Daemon DAD proxy daemon responsible for forwarding NS/NA between tethered iface and upstream iface. Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1258645 Merged-In: I3f1d7019f1a12647b78630a412df3adf03e9e95a Change-Id: I208bcd3f320afb2673ea0a1cbbccd5f32059fe8b --- .../jni/android_net_util_TetheringUtils.cpp | 52 ++- Tethering/src/android/net/ip/DadProxy.java | 54 +++ Tethering/src/android/net/ip/IpServer.java | 45 ++- .../net/ip/NeighborPacketForwarder.java | 180 ++++++++++ .../net/ip/RouterAdvertisementDaemon.java | 16 +- .../src/android/net/util/TetheringUtils.java | 33 ++ Tethering/tests/privileged/Android.bp | 21 +- .../src/android/net/ip/DadProxyTest.java | 338 ++++++++++++++++++ .../unit/src/android/net/ip/IpServerTest.java | 91 +++++ .../networkstack/tethering/TetheringTest.java | 13 + 10 files changed, 818 insertions(+), 25 deletions(-) create mode 100644 Tethering/src/android/net/ip/DadProxy.java create mode 100644 Tethering/src/android/net/ip/NeighborPacketForwarder.java create mode 100644 Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java diff --git a/Tethering/jni/android_net_util_TetheringUtils.cpp b/Tethering/jni/android_net_util_TetheringUtils.cpp index 5493440644..60dacd4f74 100644 --- a/Tethering/jni/android_net_util_TetheringUtils.cpp +++ b/Tethering/jni/android_net_util_TetheringUtils.cpp @@ -17,17 +17,62 @@ #include #include #include +#include #include #include #include +#include +#include #include #include +#include #define LOG_TAG "TetheringUtils" #include namespace android { +static const uint32_t kIPv6NextHeaderOffset = offsetof(ip6_hdr, ip6_nxt); +static const uint32_t kIPv6PayloadStart = sizeof(ip6_hdr); +static const uint32_t kICMPv6TypeOffset = kIPv6PayloadStart + offsetof(icmp6_hdr, icmp6_type); + +static void android_net_util_setupIcmpFilter(JNIEnv *env, jobject javaFd, uint32_t type) { + sock_filter filter_code[] = { + // Check header is ICMPv6. + BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv6NextHeaderOffset), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3), + + // Check ICMPv6 type. + BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kICMPv6TypeOffset), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, type, 0, 1), + + // Accept or reject. + BPF_STMT(BPF_RET | BPF_K, 0xffff), + BPF_STMT(BPF_RET | BPF_K, 0) + }; + + const sock_fprog filter = { + sizeof(filter_code) / sizeof(filter_code[0]), + filter_code, + }; + + int fd = jniGetFDFromFileDescriptor(env, javaFd); + if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) { + jniThrowExceptionFmt(env, "java/net/SocketException", + "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno)); + } +} + +static void android_net_util_setupNaSocket(JNIEnv *env, jobject clazz, jobject javaFd) +{ + android_net_util_setupIcmpFilter(env, javaFd, ND_NEIGHBOR_ADVERT); +} + +static void android_net_util_setupNsSocket(JNIEnv *env, jobject clazz, jobject javaFd) +{ + android_net_util_setupIcmpFilter(env, javaFd, ND_NEIGHBOR_SOLICIT); +} + static void android_net_util_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd, jint ifIndex) { @@ -124,7 +169,12 @@ static void android_net_util_setupRaSocket(JNIEnv *env, jobject clazz, jobject j */ static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ - { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_util_setupRaSocket }, + { "setupNaSocket", "(Ljava/io/FileDescriptor;)V", + (void*) android_net_util_setupNaSocket }, + { "setupNsSocket", "(Ljava/io/FileDescriptor;)V", + (void*) android_net_util_setupNsSocket }, + { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", + (void*) android_net_util_setupRaSocket }, }; int register_android_net_util_TetheringUtils(JNIEnv* env) { diff --git a/Tethering/src/android/net/ip/DadProxy.java b/Tethering/src/android/net/ip/DadProxy.java new file mode 100644 index 0000000000..e2976b7890 --- /dev/null +++ b/Tethering/src/android/net/ip/DadProxy.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ip; + +import android.net.util.InterfaceParams; +import android.os.Handler; + +import androidx.annotation.VisibleForTesting; + +/** + * Basic Duplicate address detection proxy. + * + * @hide + */ +public class DadProxy { + private static final String TAG = DadProxy.class.getSimpleName(); + + @VisibleForTesting + public static NeighborPacketForwarder naForwarder; + public static NeighborPacketForwarder nsForwarder; + + public DadProxy(Handler h, InterfaceParams tetheredIface) { + naForwarder = new NeighborPacketForwarder(h, tetheredIface, + NeighborPacketForwarder.ICMPV6_NEIGHBOR_ADVERTISEMENT); + nsForwarder = new NeighborPacketForwarder(h, tetheredIface, + NeighborPacketForwarder.ICMPV6_NEIGHBOR_SOLICITATION); + } + + /** Stop NS/NA Forwarders. */ + public void stop() { + naForwarder.stop(); + nsForwarder.stop(); + } + + /** Set upstream iface on both forwarders. */ + public void setUpstreamIface(InterfaceParams upstreamIface) { + naForwarder.setUpstreamIface(upstreamIface); + nsForwarder.setUpstreamIface(upstreamIface); + } +} diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java index 66f216b160..52d59fcdc1 100644 --- a/Tethering/src/android/net/ip/IpServer.java +++ b/Tethering/src/android/net/ip/IpServer.java @@ -51,6 +51,7 @@ import android.net.util.InterfaceParams; import android.net.util.InterfaceSet; import android.net.util.PrefixUtils; import android.net.util.SharedLog; +import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -160,6 +161,15 @@ public class IpServer extends StateMachine { /** Capture IpServer dependencies, for injection. */ public abstract static class Dependencies { + /** + * Create a DadProxy instance to be used by IpServer. + * To support multiple tethered interfaces concurrently DAD Proxy + * needs to be supported per IpServer instead of per upstream. + */ + public DadProxy getDadProxy(Handler handler, InterfaceParams ifParams) { + return new DadProxy(handler, ifParams); + } + /** Create an IpNeighborMonitor to be used by this IpServer */ public IpNeighborMonitor getIpNeighborMonitor(Handler handler, SharedLog log, IpNeighborMonitor.NeighborEventConsumer consumer) { @@ -256,6 +266,7 @@ public class IpServer extends StateMachine { // Advertisements (otherwise, we do not add them to mLinkProperties at all). private LinkProperties mLastIPv6LinkProperties; private RouterAdvertisementDaemon mRaDaemon; + private DadProxy mDadProxy; // To be accessed only on the handler thread private int mDhcpServerStartIndex = 0; @@ -674,6 +685,13 @@ public class IpServer extends StateMachine { return false; } + // TODO: use ShimUtils instead of explicitly checking the version here. + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R || "S".equals(Build.VERSION.CODENAME) + || "T".equals(Build.VERSION.CODENAME)) { + // DAD Proxy starts forwarding packets after IPv6 upstream is present. + mDadProxy = mDeps.getDadProxy(getHandler(), mInterfaceParams); + } + return true; } @@ -685,6 +703,11 @@ public class IpServer extends StateMachine { mRaDaemon.stop(); mRaDaemon = null; } + + if (mDadProxy != null) { + mDadProxy.stop(); + mDadProxy = null; + } } // IPv6TetheringCoordinator sends updates with carefully curated IPv6-only @@ -702,11 +725,16 @@ public class IpServer extends StateMachine { } RaParams params = null; - int upstreamIfindex = 0; + String upstreamIface = null; + InterfaceParams upstreamIfaceParams = null; + int upstreamIfIndex = 0; if (v6only != null) { - final String upstreamIface = v6only.getInterfaceName(); - + upstreamIface = v6only.getInterfaceName(); + upstreamIfaceParams = mDeps.getInterfaceParams(upstreamIface); + if (upstreamIfaceParams != null) { + upstreamIfIndex = upstreamIfaceParams.index; + } params = new RaParams(); params.mtu = v6only.getMtu(); params.hasDefaultRoute = v6only.hasIpv6DefaultRoute(); @@ -726,15 +754,13 @@ public class IpServer extends StateMachine { } } - upstreamIfindex = mDeps.getIfindex(upstreamIface); - // 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); + mBpfCoordinator.addUpstreamNameToLookupTable(upstreamIfIndex, upstreamIface); } // If v6only is null, we pass in null to setRaParams(), which handles @@ -743,8 +769,11 @@ public class IpServer extends StateMachine { setRaParams(params); mLastIPv6LinkProperties = v6only; - updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, upstreamIfindex, null); - mLastIPv6UpstreamIfindex = upstreamIfindex; + updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, upstreamIfIndex, null); + mLastIPv6UpstreamIfindex = upstreamIfIndex; + if (mDadProxy != null) { + mDadProxy.setUpstreamIface(upstreamIfaceParams); + } } private void removeRoutesFromLocalNetwork(@NonNull final List toBeRemoved) { diff --git a/Tethering/src/android/net/ip/NeighborPacketForwarder.java b/Tethering/src/android/net/ip/NeighborPacketForwarder.java new file mode 100644 index 0000000000..73fc833fab --- /dev/null +++ b/Tethering/src/android/net/ip/NeighborPacketForwarder.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ip; + +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.AF_PACKET; +import static android.system.OsConstants.ETH_P_IPV6; +import static android.system.OsConstants.IPPROTO_RAW; +import static android.system.OsConstants.SOCK_DGRAM; +import static android.system.OsConstants.SOCK_NONBLOCK; +import static android.system.OsConstants.SOCK_RAW; + +import android.net.util.InterfaceParams; +import android.net.util.PacketReader; +import android.net.util.SocketUtils; +import android.net.util.TetheringUtils; +import android.os.Handler; +import android.system.ErrnoException; +import android.system.Os; +import android.util.Log; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.Inet6Address; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Arrays; + +/** + * Basic IPv6 Neighbor Advertisement Forwarder. + * + * Forward NA packets from upstream iface to tethered iface + * and NS packets from tethered iface to upstream iface. + * + * @hide + */ +public class NeighborPacketForwarder extends PacketReader { + private final String mTag; + + private FileDescriptor mFd; + + // TODO: get these from NetworkStackConstants. + private static final int IPV6_ADDR_LEN = 16; + private static final int IPV6_DST_ADDR_OFFSET = 24; + private static final int IPV6_HEADER_LEN = 40; + private static final int ETH_HEADER_LEN = 14; + + private InterfaceParams mListenIfaceParams, mSendIfaceParams; + + private final int mType; + public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136; + public static final int ICMPV6_NEIGHBOR_SOLICITATION = 135; + + public NeighborPacketForwarder(Handler h, InterfaceParams tetheredInterface, int type) { + super(h); + mTag = NeighborPacketForwarder.class.getSimpleName() + "-" + + tetheredInterface.name + "-" + type; + mType = type; + + if (mType == ICMPV6_NEIGHBOR_ADVERTISEMENT) { + mSendIfaceParams = tetheredInterface; + } else { + mListenIfaceParams = tetheredInterface; + } + } + + /** Set new upstream iface and start/stop based on new params. */ + public void setUpstreamIface(InterfaceParams upstreamParams) { + final InterfaceParams oldUpstreamParams; + + if (mType == ICMPV6_NEIGHBOR_ADVERTISEMENT) { + oldUpstreamParams = mListenIfaceParams; + mListenIfaceParams = upstreamParams; + } else { + oldUpstreamParams = mSendIfaceParams; + mSendIfaceParams = upstreamParams; + } + + if (oldUpstreamParams == null && upstreamParams != null) { + start(); + } else if (oldUpstreamParams != null && upstreamParams == null) { + stop(); + } else if (oldUpstreamParams != null && upstreamParams != null + && oldUpstreamParams.index != upstreamParams.index) { + stop(); + start(); + } + } + + // TODO: move NetworkStackUtils.closeSocketQuietly to + // frameworks/libs/net/common/device/com/android/net/module/util/[someclass]. + private void closeSocketQuietly(FileDescriptor fd) { + try { + SocketUtils.closeSocket(fd); + } catch (IOException ignored) { + } + } + + @Override + protected FileDescriptor createFd() { + try { + // ICMPv6 packets from modem do not have eth header, so RAW socket cannot be used. + // To keep uniformity in both directions PACKET socket can be used. + mFd = Os.socket(AF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, 0); + + // TODO: convert setup*Socket to setupICMPv6BpfFilter with filter type? + if (mType == ICMPV6_NEIGHBOR_ADVERTISEMENT) { + TetheringUtils.setupNaSocket(mFd); + } else if (mType == ICMPV6_NEIGHBOR_SOLICITATION) { + TetheringUtils.setupNsSocket(mFd); + } + + SocketAddress bindAddress = SocketUtils.makePacketSocketAddress( + ETH_P_IPV6, mListenIfaceParams.index); + Os.bind(mFd, bindAddress); + } catch (ErrnoException | SocketException e) { + Log.wtf(mTag, "Failed to create socket", e); + closeSocketQuietly(mFd); + return null; + } + + return mFd; + } + + private Inet6Address getIpv6DestinationAddress(byte[] recvbuf) { + Inet6Address dstAddr; + try { + dstAddr = (Inet6Address) Inet6Address.getByAddress(Arrays.copyOfRange(recvbuf, + IPV6_DST_ADDR_OFFSET, IPV6_DST_ADDR_OFFSET + IPV6_ADDR_LEN)); + } catch (UnknownHostException | ClassCastException impossible) { + throw new AssertionError("16-byte array not valid IPv6 address?"); + } + return dstAddr; + } + + @Override + protected void handlePacket(byte[] recvbuf, int length) { + if (mSendIfaceParams == null) { + return; + } + + // The BPF filter should already have checked the length of the packet, but... + if (length < IPV6_HEADER_LEN) { + return; + } + Inet6Address destv6 = getIpv6DestinationAddress(recvbuf); + if (!destv6.isMulticastAddress()) { + return; + } + InetSocketAddress dest = new InetSocketAddress(destv6, 0); + + FileDescriptor fd = null; + try { + fd = Os.socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_RAW); + SocketUtils.bindSocketToInterface(fd, mSendIfaceParams.name); + + int ret = Os.sendto(fd, recvbuf, 0, length, 0, dest); + } catch (ErrnoException | SocketException e) { + Log.e(mTag, "handlePacket error: " + e); + } finally { + closeSocketQuietly(fd); + } + } +} diff --git a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java index 6f017dcb62..7c0b7cc751 100644 --- a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java +++ b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java @@ -18,6 +18,7 @@ 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; import static android.system.OsConstants.IPPROTO_ICMPV6; import static android.system.OsConstants.SOCK_RAW; @@ -44,7 +45,6 @@ import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketException; -import java.net.UnknownHostException; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -92,10 +92,6 @@ public class RouterAdvertisementDaemon { private static final int DAY_IN_SECONDS = 86_400; - private static final byte[] ALL_NODES = new byte[] { - (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 - }; - private final InterfaceParams mInterface; private final InetSocketAddress mAllNodes; @@ -240,7 +236,6 @@ public class RouterAdvertisementDaemon { } } - public RouterAdvertisementDaemon(InterfaceParams ifParams) { mInterface = ifParams; mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mInterface.index), 0); @@ -363,15 +358,6 @@ public class RouterAdvertisementDaemon { } } - private static Inet6Address getAllNodesForScopeId(int scopeId) { - try { - return Inet6Address.getByAddress("ff02::1", ALL_NODES, scopeId); - } catch (UnknownHostException uhe) { - Log.wtf(TAG, "Failed to construct ff02::1 InetAddress: " + uhe); - return null; - } - } - private static byte asByte(int value) { return (byte) value; } diff --git a/Tethering/src/android/net/util/TetheringUtils.java b/Tethering/src/android/net/util/TetheringUtils.java index b17b4ba77c..53b54f7de0 100644 --- a/Tethering/src/android/net/util/TetheringUtils.java +++ b/Tethering/src/android/net/util/TetheringUtils.java @@ -17,11 +17,15 @@ package android.net.util; import android.net.TetherStatsParcel; import android.net.TetheringRequestParcel; +import android.util.Log; import androidx.annotation.NonNull; import java.io.FileDescriptor; +import java.net.Inet6Address; import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Arrays; import java.util.Objects; /** @@ -30,6 +34,24 @@ import java.util.Objects; * {@hide} */ public class TetheringUtils { + public static final byte[] ALL_NODES = new byte[] { + (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 + }; + + /** + * Configures a socket for receiving and sending ICMPv6 neighbor advertisments. + * @param fd the socket's {@link FileDescriptor}. + */ + public static native void setupNaSocket(FileDescriptor fd) + throws SocketException; + + /** + * Configures a socket for receiving and sending ICMPv6 neighbor solicitations. + * @param fd the socket's {@link FileDescriptor}. + */ + public static native void setupNsSocket(FileDescriptor fd) + throws SocketException; + /** * The object which records offload Tx/Rx forwarded bytes/packets. * TODO: Replace the inner class ForwardedStats of class OffloadHardwareInterface with @@ -129,4 +151,15 @@ public class TetheringUtils { && request.exemptFromEntitlementCheck == otherRequest.exemptFromEntitlementCheck && request.showProvisioningUi == otherRequest.showProvisioningUi; } + + /** Get inet6 address for all nodes given scope ID. */ + public static Inet6Address getAllNodesForScopeId(int scopeId) { + try { + return Inet6Address.getByAddress("ff02::1", ALL_NODES, scopeId); + } catch (UnknownHostException uhe) { + Log.wtf("TetheringUtils", "Failed to construct Inet6Address from " + + Arrays.toString(ALL_NODES) + " and scopedId " + scopeId); + return null; + } + } } diff --git a/Tethering/tests/privileged/Android.bp b/Tethering/tests/privileged/Android.bp index a0fb24603a..9217345dc2 100644 --- a/Tethering/tests/privileged/Android.bp +++ b/Tethering/tests/privileged/Android.bp @@ -14,8 +14,22 @@ // limitations under the License. // +java_defaults { + name: "TetheringPrivilegedTestsJniDefaults", + jni_libs: [ + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + "libtetherutilsjni", + ], + jni_uses_sdk_apis: true, + visibility: ["//visibility:private"], +} + android_test { name: "TetheringPrivilegedTests", + defaults: [ + "TetheringPrivilegedTestsJniDefaults", + ], srcs: [ "src/**/*.java", "src/**/*.kt", @@ -23,8 +37,13 @@ android_test { certificate: "networkstack", platform_apis: true, test_suites: [ - "general-tests", + "device-tests", "mts", ], + static_libs: [ + "androidx.test.rules", + "net-tests-utils", + "TetheringApiCurrentLib", + ], compile_multilib: "both", } diff --git a/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java b/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java new file mode 100644 index 0000000000..747d3e8030 --- /dev/null +++ b/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ip; + +import static android.system.OsConstants.IPPROTO_ICMPV6; +import static android.system.OsConstants.IPPROTO_TCP; + +import static com.android.internal.util.BitUtils.uint16; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.app.Instrumentation; +import android.content.Context; +import android.net.INetd; +import android.net.InetAddresses; +import android.net.MacAddress; +import android.net.TestNetworkInterface; +import android.net.TestNetworkManager; +import android.net.util.InterfaceParams; +import android.net.util.IpUtils; +import android.net.util.TetheringUtils; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.system.ErrnoException; +import android.system.Os; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.testutils.TapPacketReader; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +import java.io.FileDescriptor; +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicReference; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DadProxyTest { + private static final int DATA_BUFFER_LEN = 4096; + private static final int PACKET_TIMEOUT_MS = 5_000; + + // TODO: make NetworkStackConstants accessible to this test and use the constant from there. + private static final int ETHER_SRC_ADDR_OFFSET = 6; + + private DadProxy mProxy; + TestNetworkInterface mUpstreamTestIface, mTetheredTestIface; + private InterfaceParams mUpstreamParams, mTetheredParams; + private HandlerThread mHandlerThread; + private Handler mHandler; + private TapPacketReader mUpstreamPacketReader, mTetheredPacketReader; + private FileDescriptor mUpstreamTapFd, mTetheredTapFd; + + private static INetd sNetd; + + @BeforeClass + public static void setupOnce() { + System.loadLibrary("tetherutilsjni"); + + final Instrumentation inst = InstrumentationRegistry.getInstrumentation(); + final IBinder netdIBinder = + (IBinder) inst.getContext().getSystemService(Context.NETD_SERVICE); + sNetd = INetd.Stub.asInterface(netdIBinder); + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mHandlerThread = new HandlerThread(getClass().getSimpleName()); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + + setupTapInterfaces(); + + // Looper must be prepared here since AndroidJUnitRunner runs tests on separate threads. + if (Looper.myLooper() == null) Looper.prepare(); + + DadProxy mProxy = setupProxy(); + } + + @After + public void tearDown() throws Exception { + if (mHandlerThread != null) { + mHandler.post(mUpstreamPacketReader::stop); // Also closes the socket + mHandler.post(mTetheredPacketReader::stop); // Also closes the socket + mUpstreamTapFd = null; + mTetheredTapFd = null; + mHandlerThread.quitSafely(); + } + + if (mTetheredParams != null) { + sNetd.networkRemoveInterface(INetd.LOCAL_NET_ID, mTetheredParams.name); + } + if (mUpstreamParams != null) { + sNetd.networkRemoveInterface(INetd.LOCAL_NET_ID, mUpstreamParams.name); + } + + if (mUpstreamTestIface != null) { + try { + Os.close(mUpstreamTestIface.getFileDescriptor().getFileDescriptor()); + } catch (ErrnoException e) { } + } + + if (mTetheredTestIface != null) { + try { + Os.close(mTetheredTestIface.getFileDescriptor().getFileDescriptor()); + } catch (ErrnoException e) { } + } + } + + private TestNetworkInterface setupTapInterface() { + final Instrumentation inst = InstrumentationRegistry.getInstrumentation(); + AtomicReference iface = new AtomicReference<>(); + + inst.getUiAutomation().adoptShellPermissionIdentity(); + try { + final TestNetworkManager tnm = (TestNetworkManager) inst.getContext().getSystemService( + Context.TEST_NETWORK_SERVICE); + iface.set(tnm.createTapInterface()); + } finally { + inst.getUiAutomation().dropShellPermissionIdentity(); + } + + return iface.get(); + } + + private void setupTapInterfaces() { + // Create upstream test iface. + mUpstreamTestIface = setupTapInterface(); + mUpstreamParams = InterfaceParams.getByName(mUpstreamTestIface.getInterfaceName()); + assertNotNull(mUpstreamParams); + mUpstreamTapFd = mUpstreamTestIface.getFileDescriptor().getFileDescriptor(); + mUpstreamPacketReader = new TapPacketReader(mHandler, mUpstreamTapFd, + DATA_BUFFER_LEN); + mHandler.post(mUpstreamPacketReader::start); + + // Create tethered test iface. + mTetheredTestIface = setupTapInterface(); + mTetheredParams = InterfaceParams.getByName(mTetheredTestIface.getInterfaceName()); + assertNotNull(mTetheredParams); + mTetheredTapFd = mTetheredTestIface.getFileDescriptor().getFileDescriptor(); + mTetheredPacketReader = new TapPacketReader(mHandler, mTetheredTapFd, + DATA_BUFFER_LEN); + mHandler.post(mTetheredPacketReader::start); + } + + private static final int IPV6_HEADER_LEN = 40; + private static final int ETH_HEADER_LEN = 14; + private static final int ICMPV6_NA_NS_LEN = 24; + private static final int LL_TARGET_OPTION_LEN = 8; + private static final int ICMPV6_CHECKSUM_OFFSET = 2; + private static final int ETHER_TYPE_IPV6 = 0x86dd; + + // TODO: move the IpUtils code to frameworks/lib/net and link it statically. + private static int checksumFold(int sum) { + while (sum > 0xffff) { + sum = (sum >> 16) + (sum & 0xffff); + } + return sum; + } + + // TODO: move the IpUtils code to frameworks/lib/net and link it statically. + private static short checksumAdjust(short checksum, short oldWord, short newWord) { + checksum = (short) ~checksum; + int tempSum = checksumFold(uint16(checksum) + uint16(newWord) + 0xffff - uint16(oldWord)); + return (short) ~tempSum; + } + + // TODO: move the IpUtils code to frameworks/lib/net and link it statically. + private static short icmpv6Checksum(ByteBuffer buf, int ipOffset, int transportOffset, + int transportLen) { + // The ICMPv6 checksum is the same as the TCP checksum, except the pseudo-header uses + // 58 (ICMPv6) instead of 6 (TCP). Calculate the TCP checksum, and then do an incremental + // checksum adjustment for the change in the next header byte. + short checksum = IpUtils.tcpChecksum(buf, ipOffset, transportOffset, transportLen); + return checksumAdjust(checksum, (short) IPPROTO_TCP, (short) IPPROTO_ICMPV6); + } + + private static ByteBuffer createDadPacket(int type) { + // Refer to buildArpPacket() + int icmpLen = ICMPV6_NA_NS_LEN + + (type == NeighborPacketForwarder.ICMPV6_NEIGHBOR_ADVERTISEMENT + ? LL_TARGET_OPTION_LEN : 0); + final ByteBuffer buf = ByteBuffer.allocate(icmpLen + IPV6_HEADER_LEN + ETH_HEADER_LEN); + + // Ethernet header. + final MacAddress srcMac = MacAddress.fromString("33:33:ff:66:77:88"); + buf.put(srcMac.toByteArray()); + final MacAddress dstMac = MacAddress.fromString("01:02:03:04:05:06"); + buf.put(dstMac.toByteArray()); + buf.putShort((short) ETHER_TYPE_IPV6); + + // IPv6 header + byte[] version = {(byte) 0x60, 0x00, 0x00, 0x00}; + buf.put(version); // Version + buf.putShort((byte) icmpLen); // Length + buf.put((byte) IPPROTO_ICMPV6); // Next header + buf.put((byte) 0xff); // Hop limit + + final byte[] target = + InetAddresses.parseNumericAddress("fe80::1122:3344:5566:7788").getAddress(); + final byte[] src; + final byte[] dst; + if (type == NeighborPacketForwarder.ICMPV6_NEIGHBOR_SOLICITATION) { + src = InetAddresses.parseNumericAddress("::").getAddress(); + dst = InetAddresses.parseNumericAddress("ff02::1:ff66:7788").getAddress(); + } else { + src = target; + dst = TetheringUtils.ALL_NODES; + } + buf.put(src); + buf.put(dst); + + // ICMPv6 Header + buf.put((byte) type); // Type + buf.put((byte) 0x00); // Code + buf.putShort((short) 0); // Checksum + buf.putInt(0); // Reserved + buf.put(target); + + if (type == NeighborPacketForwarder.ICMPV6_NEIGHBOR_ADVERTISEMENT) { + //NA packet has LL target address + //ICMPv6 Option + buf.put((byte) 0x02); // Type + buf.put((byte) 0x01); // Length + byte[] ll_target = MacAddress.fromString("01:02:03:04:05:06").toByteArray(); + buf.put(ll_target); + } + + // Populate checksum field + final int transportOffset = ETH_HEADER_LEN + IPV6_HEADER_LEN; + final short checksum = icmpv6Checksum(buf, ETH_HEADER_LEN, transportOffset, icmpLen); + buf.putShort(transportOffset + ICMPV6_CHECKSUM_OFFSET, checksum); + + buf.flip(); + return buf; + } + + private DadProxy setupProxy() throws Exception { + DadProxy proxy = new DadProxy(mHandler, mTetheredParams); + mHandler.post(() -> proxy.setUpstreamIface(mUpstreamParams)); + + // Upstream iface is added to local network to simplify test case. + // Otherwise the test needs to create and destroy a network for the upstream iface. + sNetd.networkAddInterface(INetd.LOCAL_NET_ID, mUpstreamParams.name); + sNetd.networkAddInterface(INetd.LOCAL_NET_ID, mTetheredParams.name); + + return proxy; + } + + // TODO: change to assert. + private boolean waitForPacket(ByteBuffer packet, TapPacketReader reader) { + byte[] p; + + while ((p = reader.popPacket(PACKET_TIMEOUT_MS)) != null) { + final ByteBuffer buffer = ByteBuffer.wrap(p); + + if (buffer.compareTo(packet) == 0) return true; + } + return false; + } + + private void updateDstMac(ByteBuffer buf, MacAddress mac) { + buf.put(mac.toByteArray()); + buf.rewind(); + } + private void updateSrcMac(ByteBuffer buf, InterfaceParams ifaceParams) { + buf.position(ETHER_SRC_ADDR_OFFSET); + buf.put(ifaceParams.macAddr.toByteArray()); + buf.rewind(); + } + + @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)); + } + + @Test + // TODO: remove test once DAD works in both directions. + 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)); + } + + @Test + public void testNsForwardingFromTetherToUpstream() throws Exception { + ByteBuffer ns = createDadPacket(NeighborPacketForwarder.ICMPV6_NEIGHBOR_SOLICITATION); + + mTetheredPacketReader.sendResponse(ns); + updateSrcMac(ns, mUpstreamParams); + assertTrue(waitForPacket(ns, mUpstreamPacketReader)); + } + + @Test + // TODO: remove test once DAD works in both directions. + public void testNsForwardingFromUpstreamToTether() throws Exception { + ByteBuffer ns = createDadPacket(NeighborPacketForwarder.ICMPV6_NEIGHBOR_SOLICITATION); + + mUpstreamPacketReader.sendResponse(ns); + updateSrcMac(ns, mUpstreamParams); + assertFalse(waitForPacket(ns, mTetheredPacketReader)); + } +} diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java index 2d9f8ed117..2eb75895ac 100644 --- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java @@ -87,6 +87,7 @@ import android.net.util.InterfaceParams; import android.net.util.InterfaceSet; import android.net.util.PrefixUtils; import android.net.util.SharedLog; +import android.os.Build; import android.os.Handler; import android.os.RemoteException; import android.os.test.TestLooper; @@ -101,8 +102,12 @@ import com.android.networkstack.tethering.BpfCoordinator; import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; import com.android.networkstack.tethering.PrivateAddressCoordinator; import com.android.networkstack.tethering.TetheringConfiguration; +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -121,6 +126,9 @@ import java.util.List; @RunWith(AndroidJUnit4.class) @SmallTest public class IpServerTest { + @Rule + public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); + private static final String IFACE_NAME = "testnet1"; private static final String UPSTREAM_IFACE = "upstream0"; private static final String UPSTREAM_IFACE2 = "upstream1"; @@ -133,6 +141,11 @@ public class IpServerTest { private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams( IFACE_NAME, 42 /* index */, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */); + private static final InterfaceParams UPSTREAM_IFACE_PARAMS = new InterfaceParams( + UPSTREAM_IFACE, UPSTREAM_IFINDEX, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */); + private static final InterfaceParams UPSTREAM_IFACE_PARAMS2 = new InterfaceParams( + UPSTREAM_IFACE2, UPSTREAM_IFINDEX2, MacAddress.ALL_ZEROS_ADDRESS, + 1500 /* defaultMtu */); private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000; @@ -143,6 +156,7 @@ public class IpServerTest { @Mock private IpServer.Callback mCallback; @Mock private SharedLog mSharedLog; @Mock private IDhcpServer mDhcpServer; + @Mock private DadProxy mDadProxy; @Mock private RouterAdvertisementDaemon mRaDaemon; @Mock private IpNeighborMonitor mIpNeighborMonitor; @Mock private IpServer.Dependencies mDependencies; @@ -166,8 +180,11 @@ public class IpServerTest { private void initStateMachine(int interfaceType, boolean usingLegacyDhcp, boolean usingBpfOffload) throws Exception { + when(mDependencies.getDadProxy(any(), any())).thenReturn(mDadProxy); when(mDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon); when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS); + when(mDependencies.getInterfaceParams(UPSTREAM_IFACE)).thenReturn(UPSTREAM_IFACE_PARAMS); + when(mDependencies.getInterfaceParams(UPSTREAM_IFACE2)).thenReturn(UPSTREAM_IFACE_PARAMS2); when(mDependencies.getIfindex(eq(UPSTREAM_IFACE))).thenReturn(UPSTREAM_IFINDEX); when(mDependencies.getIfindex(eq(UPSTREAM_IFACE2))).thenReturn(UPSTREAM_IFINDEX2); @@ -1107,4 +1124,78 @@ public class IpServerTest { } return true; } + + @Test @IgnoreUpTo(Build.VERSION_CODES.R) + public void dadProxyUpdates() throws Exception { + InOrder inOrder = inOrder(mDadProxy); + initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); + inOrder.verify(mDadProxy).setUpstreamIface(UPSTREAM_IFACE_PARAMS); + + // Add an upstream without IPv6. + dispatchTetherConnectionChanged(UPSTREAM_IFACE, null, 0); + inOrder.verify(mDadProxy).setUpstreamIface(null); + + // Add IPv6 to the upstream. + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(UPSTREAM_IFACE); + dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, 0); + inOrder.verify(mDadProxy).setUpstreamIface(UPSTREAM_IFACE_PARAMS); + + // Change upstream. + // New linkproperties is needed, otherwise changing the iface has no impact. + LinkProperties lp2 = new LinkProperties(); + lp2.setInterfaceName(UPSTREAM_IFACE2); + dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp2, 0); + inOrder.verify(mDadProxy).setUpstreamIface(UPSTREAM_IFACE_PARAMS2); + + // Lose IPv6 on the upstream... + dispatchTetherConnectionChanged(UPSTREAM_IFACE2, null, 0); + inOrder.verify(mDadProxy).setUpstreamIface(null); + + // ... and regain it on a different upstream. + dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, 0); + inOrder.verify(mDadProxy).setUpstreamIface(UPSTREAM_IFACE_PARAMS); + + // Lose upstream. + dispatchTetherConnectionChanged(null, null, 0); + inOrder.verify(mDadProxy).setUpstreamIface(null); + + // Regain upstream. + dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, 0); + inOrder.verify(mDadProxy).setUpstreamIface(UPSTREAM_IFACE_PARAMS); + + // Stop tethering. + mIpServer.stop(); + mLooper.dispatchAll(); + } + + private void checkDadProxyEnabled(boolean expectEnabled) throws Exception { + initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); + InOrder inOrder = inOrder(mDadProxy); + // Add IPv6 to the upstream. + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(UPSTREAM_IFACE); + if (expectEnabled) { + inOrder.verify(mDadProxy).setUpstreamIface(UPSTREAM_IFACE_PARAMS); + } else { + inOrder.verifyNoMoreInteractions(); + } + // Stop tethering. + mIpServer.stop(); + mLooper.dispatchAll(); + if (expectEnabled) { + inOrder.verify(mDadProxy).stop(); + } + else { + verify(mDependencies, never()).getDadProxy(any(), any()); + } + } + @Test @IgnoreAfter(Build.VERSION_CODES.R) + public void testDadProxyUpdates_DisabledUpToR() throws Exception { + checkDadProxyEnabled(false); + } + @Test @IgnoreUpTo(Build.VERSION_CODES.R) + public void testDadProxyUpdates_EnabledAfterR() throws Exception { + checkDadProxyEnabled(true); + } } 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 fed0a550e4..114cb7ca6e 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -113,6 +113,7 @@ import android.net.TetheringRequestParcel; import android.net.dhcp.DhcpServerCallbacks; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpServer; +import android.net.ip.DadProxy; import android.net.ip.IpNeighborMonitor; import android.net.ip.IpServer; import android.net.ip.RouterAdvertisementDaemon; @@ -200,6 +201,7 @@ public class TetheringTest { @Mock private CarrierConfigManager mCarrierConfigManager; @Mock private UpstreamNetworkMonitor mUpstreamNetworkMonitor; @Mock private IPv6TetheringCoordinator mIPv6TetheringCoordinator; + @Mock private DadProxy mDadProxy; @Mock private RouterAdvertisementDaemon mRouterAdvertisementDaemon; @Mock private IpNeighborMonitor mIpNeighborMonitor; @Mock private IDhcpServer mDhcpServer; @@ -284,6 +286,12 @@ public class TetheringTest { } public class MockIpServerDependencies extends IpServer.Dependencies { + @Override + public DadProxy getDadProxy( + Handler handler, InterfaceParams ifParams) { + return mDadProxy; + } + @Override public RouterAdvertisementDaemon getRouterAdvertisementDaemon( InterfaceParams ifParams) { @@ -845,6 +853,7 @@ public class TetheringTest { verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); sendIPv6TetherUpdates(upstreamState); + verify(mDadProxy, never()).setUpstreamIface(notNull()); verify(mRouterAdvertisementDaemon, never()).buildNewRa(any(), notNull()); verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks( any(), any()); @@ -871,6 +880,8 @@ public class TetheringTest { verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); sendIPv6TetherUpdates(upstreamState); + // TODO: add interfaceParams to compare in verify. + verify(mDadProxy, times(1)).setUpstreamIface(notNull()); verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull()); verify(mNetd, times(1)).tetherApplyDnsInterfaces(); } @@ -887,6 +898,7 @@ public class TetheringTest { any(), any()); sendIPv6TetherUpdates(upstreamState); + verify(mDadProxy, times(1)).setUpstreamIface(notNull()); verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull()); verify(mNetd, times(1)).tetherApplyDnsInterfaces(); } @@ -904,6 +916,7 @@ public class TetheringTest { verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); sendIPv6TetherUpdates(upstreamState); + verify(mDadProxy, times(1)).setUpstreamIface(notNull()); verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull()); verify(mNetd, times(1)).tetherApplyDnsInterfaces(); } From 2dc831401fd3459f3fea9d4fe986cef10fdbe304 Mon Sep 17 00:00:00 2001 From: markchien Date: Wed, 4 Nov 2020 22:04:41 +0800 Subject: [PATCH 3/6] tethering: offload: Netlink Req NfGen Add the netfilter generic message header to the netlink req. This is needed so the kernel won't ignore the request for invalid params. Bug: 149109043 Test: ConntrackSocketTest Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1424920/ Merged-In: I3f1d7019f1a12647b78630a412df3adf03e9e95a Change-Id: I63ca166851d6ca2c4737b263dde14c8d794a7d10 --- .../tethering/OffloadHardwareInterface.java | 28 ++-- .../tethering/ConntrackSocketTest.java | 131 ++++++++++++++++++ .../OffloadHardwareInterfaceTest.java | 2 +- 3 files changed, 151 insertions(+), 10 deletions(-) create mode 100644 Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java index 33b9d00e70..da5f25b2a5 100644 --- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java +++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java @@ -28,6 +28,7 @@ import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate; import android.hardware.tetheroffload.control.V1_0.NetworkProtocol; import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent; import android.net.netlink.NetlinkSocket; +import android.net.netlink.StructNfGenMsg; import android.net.netlink.StructNlMsgHdr; import android.net.util.SharedLog; import android.net.util.SocketUtils; @@ -41,11 +42,12 @@ import android.system.OsConstants; import com.android.internal.annotations.VisibleForTesting; import java.io.FileDescriptor; -import java.io.InterruptedIOException; import java.io.IOException; +import java.io.InterruptedIOException; import java.net.SocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; import java.util.NoSuchElementException; @@ -66,11 +68,12 @@ public class OffloadHardwareInterface { private static final String NO_IPV4_ADDRESS = ""; private static final String NO_IPV4_GATEWAY = ""; // Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h - private static final int NF_NETLINK_CONNTRACK_NEW = 1; - private static final int NF_NETLINK_CONNTRACK_UPDATE = 2; - private static final int NF_NETLINK_CONNTRACK_DESTROY = 4; + public static final int NF_NETLINK_CONNTRACK_NEW = 1; + public static final int NF_NETLINK_CONNTRACK_UPDATE = 2; + public static final int NF_NETLINK_CONNTRACK_DESTROY = 4; // Reference libnetfilter_conntrack/linux_nfnetlink_conntrack.h public static final short NFNL_SUBSYS_CTNETLINK = 1; + public static final short IPCTNL_MSG_CT_NEW = 0; public static final short IPCTNL_MSG_CT_GET = 1; private final long NETLINK_MESSAGE_TIMEOUT_MS = 500; @@ -237,7 +240,7 @@ public class OffloadHardwareInterface { NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY); if (h1 == null) return false; - sendNetlinkMessage(h1, (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET), + sendIpv4NfGenMsg(h1, (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET), (short) (NLM_F_REQUEST | NLM_F_DUMP)); final NativeHandle h2 = mDeps.createConntrackSocket( @@ -267,16 +270,23 @@ public class OffloadHardwareInterface { } @VisibleForTesting - public void sendNetlinkMessage(@NonNull NativeHandle handle, short type, short flags) { - final int length = StructNlMsgHdr.STRUCT_SIZE; + public void sendIpv4NfGenMsg(@NonNull NativeHandle handle, short type, short flags) { + final int length = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE; final byte[] msg = new byte[length]; - final StructNlMsgHdr nlh = new StructNlMsgHdr(); final ByteBuffer byteBuffer = ByteBuffer.wrap(msg); + byteBuffer.order(ByteOrder.nativeOrder()); + + final StructNlMsgHdr nlh = new StructNlMsgHdr(); nlh.nlmsg_len = length; nlh.nlmsg_type = type; nlh.nlmsg_flags = flags; - nlh.nlmsg_seq = 1; + nlh.nlmsg_seq = 0; nlh.pack(byteBuffer); + + // Header needs to be added to buffer since a generic netlink request is being sent. + final StructNfGenMsg nfh = new StructNfGenMsg((byte) OsConstants.AF_INET); + nfh.pack(byteBuffer); + try { NetlinkSocket.sendMessage(handle.getFileDescriptor(), msg, 0 /* offset */, length, NETLINK_MESSAGE_TIMEOUT_MS); diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java new file mode 100644 index 0000000000..2b272bc040 --- /dev/null +++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.networkstack.tethering; + +import static android.net.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE; +import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP; +import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST; + +import static com.android.networkstack.tethering.OffloadHardwareInterface.IPCTNL_MSG_CT_GET; +import static com.android.networkstack.tethering.OffloadHardwareInterface.IPCTNL_MSG_CT_NEW; +import static com.android.networkstack.tethering.OffloadHardwareInterface.NFNL_SUBSYS_CTNETLINK; +import static com.android.networkstack.tethering.OffloadHardwareInterface.NF_NETLINK_CONNTRACK_DESTROY; +import static com.android.networkstack.tethering.OffloadHardwareInterface.NF_NETLINK_CONNTRACK_NEW; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.net.netlink.StructNlMsgHdr; +import android.net.util.SharedLog; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.NativeHandle; +import android.system.Os; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ConntrackSocketTest { + private static final long TIMEOUT = 500; + + private HandlerThread mHandlerThread; + private Handler mHandler; + private final SharedLog mLog = new SharedLog("privileged-test"); + + private OffloadHardwareInterface mOffloadHw; + private OffloadHardwareInterface.Dependencies mDeps; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mHandlerThread = new HandlerThread(getClass().getSimpleName()); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + + // Looper must be prepared here since AndroidJUnitRunner runs tests on separate threads. + if (Looper.myLooper() == null) Looper.prepare(); + + mDeps = new OffloadHardwareInterface.Dependencies(mLog); + mOffloadHw = new OffloadHardwareInterface(mHandler, mLog, mDeps); + } + + @Test + public void testIpv4ConntrackSocket() throws Exception { + // Set up server and connect. + final InetSocketAddress anyAddress = new InetSocketAddress( + InetAddress.getByName("127.0.0.1"), 0); + final ServerSocket serverSocket = new ServerSocket(); + serverSocket.bind(anyAddress); + final SocketAddress theAddress = serverSocket.getLocalSocketAddress(); + + // Make a connection to the server. + final Socket socket = new Socket(); + socket.connect(theAddress); + final Socket acceptedSocket = serverSocket.accept(); + + final NativeHandle handle = mDeps.createConntrackSocket( + NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY); + mOffloadHw.sendIpv4NfGenMsg(handle, + (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET), + (short) (NLM_F_REQUEST | NLM_F_DUMP)); + + boolean foundConntrackEntry = false; + ByteBuffer buffer = ByteBuffer.allocate(DEFAULT_RECV_BUFSIZE); + buffer.order(ByteOrder.nativeOrder()); + + try { + while (Os.read(handle.getFileDescriptor(), buffer) > 0) { + buffer.flip(); + + // TODO: ConntrackMessage should get a parse API like StructNlMsgHdr + // so we can confirm that the conntrack added is for the TCP connection above. + final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(buffer); + assertNotNull(nlmsghdr); + + // As long as 1 conntrack entry is found test case will pass, even if it's not + // the from the TCP connection above. + if (nlmsghdr.nlmsg_type == ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_NEW)) { + foundConntrackEntry = true; + break; + } + } + } + finally { + socket.close(); + serverSocket.close(); + } + assertTrue("Did not receive any NFNL_SUBSYS_CTNETLINK/IPCTNL_MSG_CT_NEW message", + foundConntrackEntry); + } +} diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java index c543fad62d..71f8f27d27 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java @@ -229,7 +229,7 @@ public final class OffloadHardwareInterfaceTest { } when(mNativeHandle.getFileDescriptor()).thenReturn(writeSocket); - mOffloadHw.sendNetlinkMessage(mNativeHandle, TEST_TYPE, TEST_FLAGS); + mOffloadHw.sendIpv4NfGenMsg(mNativeHandle, TEST_TYPE, TEST_FLAGS); ByteBuffer buffer = ByteBuffer.allocate(StructNlMsgHdr.STRUCT_SIZE); int read = Os.read(readSocket, buffer); From 1d5b21140c716e144359433e112985ae379c5768 Mon Sep 17 00:00:00 2001 From: markchien Date: Wed, 4 Nov 2020 22:07:17 +0800 Subject: [PATCH 4/6] Fix OffloadHardwareInterfaceTest. r.android.com/1424920 changed the code but forgot to update the unit test. Also fix some lint errors. Bug: 149109043 Test: atest TetheringTests TetheringPrivilegedTests Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1440767 Merged-In: I3f1d7019f1a12647b78630a412df3adf03e9e95a Change-Id: I9fb3be173ddb9314334dab72aebc86c22b7c5aeb --- .../tethering/ConntrackSocketTest.java | 3 +-- .../OffloadHardwareInterfaceTest.java | 24 +++++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java index 2b272bc040..57c28fc67c 100644 --- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java +++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java @@ -120,8 +120,7 @@ public class ConntrackSocketTest { break; } } - } - finally { + } finally { socket.close(); serverSocket.close(); } diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java index 71f8f27d27..38b19dd3da 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java @@ -17,8 +17,9 @@ package com.android.networkstack.tethering; import static android.net.util.TetheringUtils.uint16; -import static android.system.OsConstants.SOCK_STREAM; +import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_UNIX; +import static android.system.OsConstants.SOCK_STREAM; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -35,14 +36,15 @@ import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback; import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate; import android.hardware.tetheroffload.control.V1_0.NetworkProtocol; import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent; +import android.net.netlink.StructNfGenMsg; import android.net.netlink.StructNlMsgHdr; import android.net.util.SharedLog; import android.os.Handler; import android.os.NativeHandle; import android.os.test.TestLooper; import android.system.ErrnoException; -import android.system.OsConstants; import android.system.Os; +import android.system.OsConstants; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -55,8 +57,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.FileDescriptor; -import java.io.OutputStream; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; @RunWith(AndroidJUnit4.class) @@ -218,7 +220,7 @@ public final class OffloadHardwareInterfaceTest { } @Test - public void testNetlinkMessage() throws Exception { + public void testSendIpv4NfGenMsg() throws Exception { FileDescriptor writeSocket = new FileDescriptor(); FileDescriptor readSocket = new FileDescriptor(); try { @@ -231,15 +233,23 @@ public final class OffloadHardwareInterfaceTest { mOffloadHw.sendIpv4NfGenMsg(mNativeHandle, TEST_TYPE, TEST_FLAGS); - ByteBuffer buffer = ByteBuffer.allocate(StructNlMsgHdr.STRUCT_SIZE); + ByteBuffer buffer = ByteBuffer.allocate(9823); // Arbitrary value > expectedLen. + buffer.order(ByteOrder.nativeOrder()); + int read = Os.read(readSocket, buffer); + final int expectedLen = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE; + assertEquals(expectedLen, read); buffer.flip(); - assertEquals(StructNlMsgHdr.STRUCT_SIZE, buffer.getInt()); + assertEquals(expectedLen, buffer.getInt()); assertEquals(TEST_TYPE, buffer.getShort()); assertEquals(TEST_FLAGS, buffer.getShort()); - assertEquals(1 /* seq */, buffer.getInt()); + assertEquals(0 /* seq */, buffer.getInt()); assertEquals(0 /* pid */, buffer.getInt()); + assertEquals(AF_INET, buffer.get()); // nfgen_family + assertEquals(0 /* error */, buffer.get()); // version + assertEquals(0 /* error */, buffer.getShort()); // res_id + assertEquals(expectedLen, buffer.position()); } private NatTimeoutUpdate buildNatTimeoutUpdate(final int proto) { From 75b84e4cb4ed4ea54566a61b183c5a01837befa7 Mon Sep 17 00:00:00 2001 From: markchien Date: Wed, 4 Nov 2020 22:09:33 +0800 Subject: [PATCH 5/6] Remove unused testutils lib from tethering tests frameworks-base-testutils is unused in tethering integration tests, so the dependency can be removed. That test library also contains test classes, so removing the dependency allows tethering tests to stop running the associated tests. Also add jarjar rules to the unit tests to zap (remove) the test classes from the output APK. Ideally the unit tests should stop depending on that library too (TestableLooper can be used instead of TestLooper), or the frameworks-base-testutils library should stop including test classes. Bug: 167968946 Test: m CtsTetheringTest TetheringTests Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1433924 Merged-In: I3f1d7019f1a12647b78630a412df3adf03e9e95a Change-Id: Id189676b7447c6cb0f8d9b216c42a34c6513ba61 --- Tethering/tests/integration/Android.bp | 1 - Tethering/tests/unit/jarjar-rules.txt | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp index ed69b7d63c..02bab9ba35 100644 --- a/Tethering/tests/integration/Android.bp +++ b/Tethering/tests/integration/Android.bp @@ -22,7 +22,6 @@ java_defaults { static_libs: [ "NetworkStackApiStableLib", "androidx.test.rules", - "frameworks-base-testutils", "mockito-target-extended-minus-junit4", "net-tests-utils", "testables", diff --git a/Tethering/tests/unit/jarjar-rules.txt b/Tethering/tests/unit/jarjar-rules.txt index ec2d2b0200..7ed89632a8 100644 --- a/Tethering/tests/unit/jarjar-rules.txt +++ b/Tethering/tests/unit/jarjar-rules.txt @@ -9,3 +9,8 @@ rule com.android.internal.util.StateMachine* com.android.networkstack.tethering. rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.tethering.util.TrafficStatsConstants@1 rule android.util.LocalLog* com.android.networkstack.tethering.util.LocalLog@1 + +# TODO: either stop using frameworks-base-testutils or remove the unit test classes it contains. +# TestableLooper from "testables" can be used instead of TestLooper from frameworks-base-testutils. +zap android.os.test.TestLooperTest* +zap com.android.test.filters.SelectTestTests* \ No newline at end of file From 4b5de730b8621b70588000c4a3a3e3a912071dd5 Mon Sep 17 00:00:00 2001 From: markchien Date: Fri, 6 Nov 2020 17:41:59 +0800 Subject: [PATCH 6/6] Add shared jarjar rule for tethering tests Also jarjar com.android.net.module.util* to com.android.networkstack.tethering.util*. Bug: 171670016 Test: atest TetheringCoverageTests Change-Id: I3bde9ad3c41adf36da99bd944303d88ce992201c Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1473223 Compare with original change, this change also add visibility rules for packages/modules/Connectivity/Tethering in new file (tests/Android.bp). Merged-In: I3f1d7019f1a12647b78630a412df3adf03e9e95a Change-Id: I40c22e2f39d795abfd961a3f797e510e51c8ed7c --- Tethering/tests/Android.bp | 24 +++++++++++++++++++++ Tethering/tests/integration/Android.bp | 3 ++- Tethering/tests/{unit => }/jarjar-rules.txt | 5 ++++- Tethering/tests/unit/Android.bp | 2 +- 4 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 Tethering/tests/Android.bp rename Tethering/tests/{unit => }/jarjar-rules.txt (87%) diff --git a/Tethering/tests/Android.bp b/Tethering/tests/Android.bp new file mode 100644 index 0000000000..731144cee0 --- /dev/null +++ b/Tethering/tests/Android.bp @@ -0,0 +1,24 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +filegroup { + name: "TetheringTestsJarJarRules", + srcs: ["jarjar-rules.txt"], + visibility: [ + "//frameworks/base/packages/Tethering/tests:__subpackages__", + "//packages/modules/Connectivity/Tethering/tests:__subpackages__", + ] +} diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp index 02bab9ba35..5765c01c43 100644 --- a/Tethering/tests/integration/Android.bp +++ b/Tethering/tests/integration/Android.bp @@ -79,6 +79,7 @@ android_test { // For NetworkStackUtils included in NetworkStackBase "libnetworkstackutilsjni", ], + jarjar_rules: ":TetheringTestsJarJarRules", compile_multilib: "both", manifest: "AndroidManifest_coverage.xml", -} \ No newline at end of file +} diff --git a/Tethering/tests/unit/jarjar-rules.txt b/Tethering/tests/jarjar-rules.txt similarity index 87% rename from Tethering/tests/unit/jarjar-rules.txt rename to Tethering/tests/jarjar-rules.txt index 7ed89632a8..c99ff7f818 100644 --- a/Tethering/tests/unit/jarjar-rules.txt +++ b/Tethering/tests/jarjar-rules.txt @@ -10,7 +10,10 @@ rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.t rule android.util.LocalLog* com.android.networkstack.tethering.util.LocalLog@1 +# Classes from net-utils-framework-common +rule com.android.net.module.util.** com.android.networkstack.tethering.util.@1 + # TODO: either stop using frameworks-base-testutils or remove the unit test classes it contains. # TestableLooper from "testables" can be used instead of TestLooper from frameworks-base-testutils. zap android.os.test.TestLooperTest* -zap com.android.test.filters.SelectTestTests* \ No newline at end of file +zap com.android.test.filters.SelectTestTests* diff --git a/Tethering/tests/unit/Android.bp b/Tethering/tests/unit/Android.bp index 637a6b6aa0..aabaa65d4a 100644 --- a/Tethering/tests/unit/Android.bp +++ b/Tethering/tests/unit/Android.bp @@ -70,7 +70,6 @@ java_defaults { "libdexmakerjvmtiagent", "libstaticjvmtiagent", ], - jarjar_rules: "jarjar-rules.txt", } // Library containing the unit tests. This is used by the coverage test target to pull in the @@ -91,6 +90,7 @@ android_test { "device-tests", "mts", ], + jarjar_rules: ":TetheringTestsJarJarRules", defaults: ["TetheringTestsDefaults"], compile_multilib: "both", }