diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2019-06-10 05:47:38 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2019-06-10 05:47:38 +0000 |
commit | 67be1d71aa5d45763746ebb46881ce29ae4c4b7d (patch) | |
tree | f98bcdf4ceb47671a72a4fe9b997fd0c1417825f | |
parent | 10c2cc18e43fb3fd15c8dd6dec58c8c8d7730c55 (diff) | |
parent | 53ed1acaa6187e6505fb400281078a5153f81342 (diff) |
Merge "Add an IpClientIntegrationTest"
-rw-r--r-- | src/android/net/dhcp/DhcpDiscoverPacket.java | 2 | ||||
-rw-r--r-- | src/android/net/dhcp/DhcpPacket.java | 28 | ||||
-rw-r--r-- | src/com/android/server/NetworkObserverRegistry.java | 12 | ||||
-rw-r--r-- | tests/integration/Android.bp | 42 | ||||
-rw-r--r-- | tests/integration/AndroidManifest.xml | 34 | ||||
-rw-r--r-- | tests/integration/AndroidTest.xml | 29 | ||||
-rw-r--r-- | tests/integration/src/android/net/ip/IpClientIntegrationTest.java | 312 | ||||
-rw-r--r-- | tests/unit/Android.bp | 15 |
8 files changed, 448 insertions, 26 deletions
diff --git a/src/android/net/dhcp/DhcpDiscoverPacket.java b/src/android/net/dhcp/DhcpDiscoverPacket.java index 11f2b61..9707d13 100644 --- a/src/android/net/dhcp/DhcpDiscoverPacket.java +++ b/src/android/net/dhcp/DhcpDiscoverPacket.java @@ -22,7 +22,7 @@ import java.nio.ByteBuffer; /** * This class implements the DHCP-DISCOVER packet. */ -class DhcpDiscoverPacket extends DhcpPacket { +public class DhcpDiscoverPacket extends DhcpPacket { /** * The IP address of the client which sent this packet. */ diff --git a/src/android/net/dhcp/DhcpPacket.java b/src/android/net/dhcp/DhcpPacket.java index a15d423..d5c3efb 100644 --- a/src/android/net/dhcp/DhcpPacket.java +++ b/src/android/net/dhcp/DhcpPacket.java @@ -108,22 +108,22 @@ public abstract class DhcpPacket { /** * The client DHCP port. */ - static final short DHCP_CLIENT = (short) 68; + public static final short DHCP_CLIENT = (short) 68; /** * The server DHCP port. */ - static final short DHCP_SERVER = (short) 67; + public static final short DHCP_SERVER = (short) 67; /** * The message op code indicating a request from a client. */ - protected static final byte DHCP_BOOTREQUEST = (byte) 1; + public static final byte DHCP_BOOTREQUEST = (byte) 1; /** * The message op code indicating a response from the server. */ - protected static final byte DHCP_BOOTREPLY = (byte) 2; + public static final byte DHCP_BOOTREPLY = (byte) 2; /** * The code type used to identify an Ethernet MAC address in the @@ -139,7 +139,7 @@ public abstract class DhcpPacket { /** * The magic cookie that identifies this as a DHCP packet instead of BOOTP. */ - private static final int DHCP_MAGIC_COOKIE = 0x63825363; + public static final int DHCP_MAGIC_COOKIE = 0x63825363; /** * DHCP Optional Type: DHCP Subnet Mask @@ -221,16 +221,16 @@ public abstract class DhcpPacket { /** * DHCP Optional Type: DHCP Message Type */ - protected static final byte DHCP_MESSAGE_TYPE = 53; + public static final byte DHCP_MESSAGE_TYPE = 53; // the actual type values - protected static final byte DHCP_MESSAGE_TYPE_DISCOVER = 1; - protected static final byte DHCP_MESSAGE_TYPE_OFFER = 2; - protected static final byte DHCP_MESSAGE_TYPE_REQUEST = 3; - protected static final byte DHCP_MESSAGE_TYPE_DECLINE = 4; - protected static final byte DHCP_MESSAGE_TYPE_ACK = 5; - protected static final byte DHCP_MESSAGE_TYPE_NAK = 6; - protected static final byte DHCP_MESSAGE_TYPE_RELEASE = 7; - protected static final byte DHCP_MESSAGE_TYPE_INFORM = 8; + public static final byte DHCP_MESSAGE_TYPE_DISCOVER = 1; + public static final byte DHCP_MESSAGE_TYPE_OFFER = 2; + public static final byte DHCP_MESSAGE_TYPE_REQUEST = 3; + public static final byte DHCP_MESSAGE_TYPE_DECLINE = 4; + public static final byte DHCP_MESSAGE_TYPE_ACK = 5; + public static final byte DHCP_MESSAGE_TYPE_NAK = 6; + public static final byte DHCP_MESSAGE_TYPE_RELEASE = 7; + public static final byte DHCP_MESSAGE_TYPE_INFORM = 8; /** * DHCP Optional Type: DHCP Server Identifier diff --git a/src/com/android/server/NetworkObserverRegistry.java b/src/com/android/server/NetworkObserverRegistry.java index afe166b..b83bc5c 100644 --- a/src/com/android/server/NetworkObserverRegistry.java +++ b/src/com/android/server/NetworkObserverRegistry.java @@ -42,19 +42,13 @@ public class NetworkObserverRegistry extends INetdUnsolicitedEventListener.Stub private static final String TAG = NetworkObserverRegistry.class.getSimpleName(); /** - * Constructs a new NetworkObserverRegistry. - * - * <p>Only one registry should be used per process since netd will silently ignore multiple - * registrations from the same process. - */ - NetworkObserverRegistry() {} - - /** * Start listening for Netd events. * * <p>This should be called before allowing any observer to be registered. + * Note there is no unregister method. The only way to unregister is when the process + * terminates. */ - void register(@NonNull INetd netd) throws RemoteException { + public void register(@NonNull INetd netd) throws RemoteException { netd.registerUnsolicitedEventListener(this); } diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp new file mode 100644 index 0000000..ec8257f --- /dev/null +++ b/tests/integration/Android.bp @@ -0,0 +1,42 @@ +// +// Copyright (C) 2019 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: "NetworkStackIntegrationTests", + certificate: "networkstack", + srcs: ["src/**/*.java"], + test_suites: ["device-tests"], + static_libs: [ + "androidx.annotation_annotation", + "androidx.test.rules", + "mockito-target-extended-minus-junit4", + "NetworkStackBase", + "testables", + ], + libs: [ + "android.test.runner", + "android.test.base", + "android.test.mock", + ], + defaults: ["libnetworkstackutilsjni_deps"], + jni_libs: [ + // For mockito extended + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + // For NetworkStackUtils included in NetworkStackBase + "libnetworkstackutilsjni", + ], +} diff --git a/tests/integration/AndroidManifest.xml b/tests/integration/AndroidManifest.xml new file mode 100644 index 0000000..bfd3735 --- /dev/null +++ b/tests/integration/AndroidManifest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 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. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.networkstack.integrationtests" + android:sharedUserId="android.uid.networkstack"> + + <!-- Note: do not add any privileged or signature permissions that are granted + to the network stack app. Otherwise, the test APK will install, but when the device is + rebooted, it will bootloop with something like: + + 05-14 00:41:02.723 18330 18330 E AndroidRuntime: java.lang.IllegalStateException: Signature|privileged permissions not in privapp-permissions whitelist: {com.android.server.networkstack.integrationtests: android.permission.CONNECTIVITY_INTERNAL} + --> + + <application android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.networkstack.integrationtests" + android:label="Networking stack integration tests"> + </instrumentation> +</manifest> diff --git a/tests/integration/AndroidTest.xml b/tests/integration/AndroidTest.xml new file mode 100644 index 0000000..c592568 --- /dev/null +++ b/tests/integration/AndroidTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 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. +--> +<configuration description="Runs Integration Tests for NetworkStack"> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="NetworkStackIntegrationTests.apk" /> + </target_preparer> + + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="framework-base-presubmit" /> + <option name="test-tag" value="NetworkStackIntegrationTests" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.server.networkstack.integrationtests" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java new file mode 100644 index 0000000..ae6afdb --- /dev/null +++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2019 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.net.dhcp.DhcpPacket.DHCP_BOOTREQUEST; +import static android.net.dhcp.DhcpPacket.DHCP_CLIENT; +import static android.net.dhcp.DhcpPacket.DHCP_MAGIC_COOKIE; +import static android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE; +import static android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_DISCOVER; +import static android.net.dhcp.DhcpPacket.DHCP_SERVER; +import static android.net.dhcp.DhcpPacket.ENCAP_L2; +import static android.net.dhcp.DhcpPacket.ETHER_BROADCAST; + +import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ALL; + +import static junit.framework.Assert.fail; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.AlarmManager; +import android.app.Instrumentation; +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.IIpMemoryStore; +import android.net.INetd; +import android.net.TestNetworkInterface; +import android.net.TestNetworkManager; +import android.net.dhcp.DhcpDiscoverPacket; +import android.net.dhcp.DhcpPacket; +import android.net.dhcp.DhcpPacket.ParseException; +import android.net.shared.ProvisioningConfiguration; +import android.net.util.InterfaceParams; +import android.net.util.PacketReader; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; + +import androidx.annotation.Nullable; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.NetworkObserverRegistry; +import com.android.server.NetworkStackService; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.FileDescriptor; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * Tests for IpClient. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class IpClientIntegrationTest { + @Mock private Context mContext; + @Mock private ConnectivityManager mCm; + @Mock private INetd mNetd; + @Mock private Resources mResources; + @Mock private IIpClientCallbacks mCb; + @Mock private AlarmManager mAlarm; + @Mock private IpClient.Dependencies mDependencies; + @Mock private ContentResolver mContentResolver; + @Mock private NetworkStackService.NetworkStackServiceManager mNetworkStackServiceManager; + @Mock private IIpMemoryStore mIpMemoryStore; + @Mock private InterfaceParams mInterfaceParams; + + private String mIfaceName; + private HandlerThread mPacketReaderThread; + private TapPacketReader mPacketReader; + private IpClient mIpc; + + private static final int DATA_BUFFER_LEN = 4096; + private static final long PACKET_TIMEOUT_MS = 5_000; + + // Ethernet header + private static final int ETH_HEADER_LEN = 14; + private static final int ETH_DEST_ADDR_OFFSET = 0; + private static final int ETH_MAC_ADDR_LEN = 6; + + // IP header + private static final int IPV4_HEADER_LEN = 20; + private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16; + private static final int IPV4_ADDR_LEN = 4; + + // UDP header + private static final int UDP_HEADER_LEN = 8; + private static final int UDP_HEADER_OFFSET = ETH_HEADER_LEN + IPV4_HEADER_LEN; + private static final int UDP_SRC_PORT_OFFSET = UDP_HEADER_OFFSET + 0; + + // DHCP header + private static final int DHCP_HEADER_OFFSET = ETH_HEADER_LEN + IPV4_HEADER_LEN + + UDP_HEADER_LEN; + private static final int DHCP_MESSAGE_OP_CODE_OFFSET = DHCP_HEADER_OFFSET + 0; + private static final int DHCP_TRANSACTION_ID_OFFSET = DHCP_HEADER_OFFSET + 4; + private static final int DHCP_OPTION_MAGIC_COOKIE_OFFSET = DHCP_HEADER_OFFSET + 236; + private static final int DHCP_OPTION_MESSAGE_TYPE_OFFSET = DHCP_OPTION_MAGIC_COOKIE_OFFSET + 4; + private static final int DHCP_OPTION_MESSAGE_TYPE_LEN_OFFSET = + DHCP_OPTION_MESSAGE_TYPE_OFFSET + 1; + private static final int DHCP_OPTION_MESSAGE_TYPE_VALUE_OFFSET = + DHCP_OPTION_MESSAGE_TYPE_OFFSET + 2; + + private static class TapPacketReader extends PacketReader { + private final ParcelFileDescriptor mTapFd; + private final LinkedBlockingQueue<byte[]> mReceivedPackets = + new LinkedBlockingQueue<byte[]>(); + + TapPacketReader(Handler h, ParcelFileDescriptor tapFd) { + super(h, DATA_BUFFER_LEN); + mTapFd = tapFd; + } + + @Override + protected FileDescriptor createFd() { + return mTapFd.getFileDescriptor(); + } + + @Override + protected void handlePacket(byte[] recvbuf, int length) { + final byte[] newPacket = Arrays.copyOf(recvbuf, length); + try { + mReceivedPackets.put(newPacket); + } catch (InterruptedException e) { + fail("fail to put the new packet in the queue"); + } + } + + /** + * Get the next packet that was received on the interface. + * + */ + @Nullable + public byte[] popPacket(long timeoutMs) { + try { + return mReceivedPackets.poll(timeoutMs, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + // Fall through + } + return null; + } + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm); + when(mContext.getSystemService(eq(ConnectivityManager.class))).thenReturn(mCm); + when(mContext.getResources()).thenReturn(mResources); + when(mDependencies.getNetd(any())).thenReturn(mNetd); + when(mContext.getContentResolver()).thenReturn(mContentResolver); + when(mDependencies.getInterfaceParams(any())).thenReturn(mInterfaceParams); + when(mNetworkStackServiceManager.getIpMemoryStoreService()).thenReturn(mIpMemoryStore); + + setUpTapInterface(); + setUpIpClient(); + } + + @After + public void tearDown() throws Exception { + if (mPacketReader != null) { + mPacketReader.stop(); // Also closes the socket + } + if (mPacketReaderThread != null) { + mPacketReaderThread.quitSafely(); + } + } + + private void setUpTapInterface() { + final Instrumentation inst = InstrumentationRegistry.getInstrumentation(); + // Adopt the shell permission identity to create a test TAP interface. + inst.getUiAutomation().adoptShellPermissionIdentity(); + + final TestNetworkInterface iface; + try { + final TestNetworkManager tnm = (TestNetworkManager) + inst.getContext().getSystemService(Context.TEST_NETWORK_SERVICE); + iface = tnm.createTapInterface(); + } finally { + // Drop the identity in order to regain the network stack permissions, which the shell + // does not have. + inst.getUiAutomation().dropShellPermissionIdentity(); + } + mIfaceName = iface.getInterfaceName(); + + mPacketReaderThread = new HandlerThread(IpClientIntegrationTest.class.getSimpleName()); + mPacketReaderThread.start(); + + final ParcelFileDescriptor tapFd = iface.getFileDescriptor(); + mPacketReader = new TapPacketReader(mPacketReaderThread.getThreadHandler(), tapFd); + mPacketReader.start(); + } + + private void setUpIpClient() throws Exception { + final Instrumentation inst = InstrumentationRegistry.getInstrumentation(); + final IBinder netdIBinder = + (IBinder) inst.getContext().getSystemService(Context.NETD_SERVICE); + final INetd netd = INetd.Stub.asInterface(netdIBinder); + when(mContext.getSystemService(eq(Context.NETD_SERVICE))).thenReturn(netdIBinder); + assertNotNull(netd); + + final NetworkObserverRegistry reg = new NetworkObserverRegistry(); + reg.register(netd); + mIpc = new IpClient(mContext, mIfaceName, mCb, reg, mNetworkStackServiceManager); + } + + private boolean packetContainsExpectedField(final byte[] packet, final int offset, + final byte[] expected) { + if (packet.length < offset + expected.length) return false; + for (int i = 0; i < expected.length; ++i) { + if (packet[offset + i] != expected[i]) return false; + } + return true; + } + + private boolean isDhcpPacket(final byte[] packet) { + final ByteBuffer buffer = ByteBuffer.wrap(packet); + + // check the packet length + if (packet.length < DHCP_HEADER_OFFSET) return false; + + // check the source port and dest port in UDP header + buffer.position(UDP_SRC_PORT_OFFSET); + final short udpSrcPort = buffer.getShort(); + final short udpDstPort = buffer.getShort(); + if (udpSrcPort != DHCP_CLIENT || udpDstPort != DHCP_SERVER) return false; + + // check DHCP message type + buffer.position(DHCP_MESSAGE_OP_CODE_OFFSET); + final byte dhcpOpCode = buffer.get(); + if (dhcpOpCode != DHCP_BOOTREQUEST) return false; + + // check DHCP magic cookie + buffer.position(DHCP_OPTION_MAGIC_COOKIE_OFFSET); + final int dhcpMagicCookie = buffer.getInt(); + if (dhcpMagicCookie != DHCP_MAGIC_COOKIE) return false; + + return true; + } + + private void verifyDhcpDiscoverPacketReceived(final byte[] packet) + throws ParseException { + assertTrue(packetContainsExpectedField(packet, ETH_DEST_ADDR_OFFSET, ETHER_BROADCAST)); + assertTrue(packetContainsExpectedField(packet, IPV4_DEST_ADDR_OFFSET, + IPV4_ADDR_ALL.getAddress())); + + // check if received dhcp packet includes DHCP Message Type option and expected + // type/length/value. + assertTrue(packet[DHCP_OPTION_MESSAGE_TYPE_OFFSET] == DHCP_MESSAGE_TYPE); + assertTrue(packet[DHCP_OPTION_MESSAGE_TYPE_OFFSET + 1] == 1); + assertTrue(packet[DHCP_OPTION_MESSAGE_TYPE_OFFSET + 2] == DHCP_MESSAGE_TYPE_DISCOVER); + final DhcpPacket dhcpPacket = DhcpPacket.decodeFullPacket( + packet, packet.length, ENCAP_L2); + assertTrue(dhcpPacket instanceof DhcpDiscoverPacket); + } + + @Test + public void testDhcpInit() throws Exception { + ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() + .withoutIpReachabilityMonitor() + .build(); + + mIpc.startProvisioning(config); + verify(mCb, times(1)).setNeighborDiscoveryOffload(true); + + byte[] packet; + while ((packet = mPacketReader.popPacket(PACKET_TIMEOUT_MS)) != null) { + try { + if (!isDhcpPacket(packet)) continue; + verifyDhcpDiscoverPacketReceived(packet); + mIpc.shutdown(); + return; + } catch (DhcpPacket.ParseException e) { + fail("parse exception: " + e); + } + } + + fail("No DHCPDISCOVER received on interface"); + } +} diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp index 48b13b0..5d90a61 100644 --- a/tests/unit/Android.bp +++ b/tests/unit/Android.bp @@ -32,11 +32,22 @@ android_test { "android.test.base", "android.test.mock", ], + defaults: ["libnetworkstackutilsjni_deps"], jni_libs: [ // For mockito extended "libdexmakerjvmtiagent", "libstaticjvmtiagent", - // For ApfTest + "libnetworkstackutilsjni", + ], +} + +// Additional dependencies of libnetworkstackutilsjni that are not provided by the system when +// running as a test application. +// Using java_defaults as jni_libs does not support filegroups. +java_defaults { + name: "libnetworkstackutilsjni_deps", + jni_libs: [ + "libartbase", "libbacktrace", "libbase", "libbinder", @@ -57,7 +68,6 @@ android_test { "libnativehelper", "libnativehelper_compat_libc++", "libnetworkstacktestsjni", - "libnetworkstackutilsjni", "libpackagelistparser", "libpcre2", "libprocessgroup", @@ -75,6 +85,7 @@ android_test { ], } +// JNI code for ApfTest cc_library_shared { name: "libnetworkstacktestsjni", srcs: [ |