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/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/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 e0009ddc1c..db503c6031 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", @@ -83,6 +82,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 69% rename from Tethering/tests/unit/jarjar-rules.txt rename to Tethering/tests/jarjar-rules.txt index ec2d2b0200..c99ff7f818 100644 --- a/Tethering/tests/unit/jarjar-rules.txt +++ b/Tethering/tests/jarjar-rules.txt @@ -9,3 +9,11 @@ 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 + +# 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* diff --git a/Tethering/tests/privileged/Android.bp b/Tethering/tests/privileged/Android.bp new file mode 100644 index 0000000000..9217345dc2 --- /dev/null +++ b/Tethering/tests/privileged/Android.bp @@ -0,0 +1,49 @@ +// +// 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. +// + +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", + ], + certificate: "networkstack", + platform_apis: true, + test_suites: [ + "device-tests", + "mts", + ], + static_libs: [ + "androidx.test.rules", + "net-tests-utils", + "TetheringApiCurrentLib", + ], + 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 @@ + + + + + + + + + + + + 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/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..57c28fc67c --- /dev/null +++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java @@ -0,0 +1,130 @@ +/* + * 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/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", } 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/OffloadHardwareInterfaceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java index c543fad62d..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 { @@ -229,17 +231,25 @@ 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(9823); // Arbitrary value > expectedLen. + buffer.order(ByteOrder.nativeOrder()); - ByteBuffer buffer = ByteBuffer.allocate(StructNlMsgHdr.STRUCT_SIZE); 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) { 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 03c1a19c00..89146b55d1 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -114,6 +114,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; @@ -201,6 +202,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; @@ -285,6 +287,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) { @@ -849,6 +857,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()); @@ -875,6 +884,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(); } @@ -891,6 +902,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(); } @@ -908,6 +920,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(); }