summaryrefslogtreecommitdiff
path: root/tests/tests/net/src/android/net/ipv6/cts/PingTest.java
blob: acf474fce493d635b59c029004771ef7b7b3f27e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/*
 * Copyright (C) 2013 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.ipv6.cts;

import android.test.AndroidTestCase;
import android.util.Log;

import libcore.io.ErrnoException;
import libcore.io.Libcore;
import libcore.io.StructTimeval;
import static libcore.io.OsConstants.*;

import java.io.FileDescriptor;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Random;

/**
 * Checks that the device has kernel support for the IPv6 ping socket. This allows ping6 to work
 * without root privileges. The necessary kernel code is in Linux 3.11 or above, or the
 * <code>common/android-3.x</code> kernel trees. If you are not running one of these kernels, the
 * functionality can be obtained by cherry-picking the following patches from David Miller's
 * <code>net-next</code> tree:
 * <ul>
 * <li>6d0bfe2 net: ipv6: Add IPv6 support to the ping socket.
 * <li>c26d6b4 ping: always initialize ->sin6_scope_id and ->sin6_flowinfo
 * <li>fbfe80c net: ipv6: fix wrong ping_v6_sendmsg return value
 * <li>a1bdc45 net: ipv6: add missing lock in ping_v6_sendmsg
 * <li>cf970c0 ping: prevent NULL pointer dereference on write to msg_name
 * </ul>
 * or the equivalent backports to the <code>common/android-3.x</code> trees.
 */
public class PingTest extends AndroidTestCase {
    /** Maximum size of the packets we're using to test. */
    private static final int MAX_SIZE = 4096;

    /** Number of packets to test. */
    private static final int NUM_PACKETS = 10;

    /** The beginning of an ICMPv6 echo request: type, code, and uninitialized checksum. */
    private static final byte[] PING_HEADER = new byte[] {
        (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00
    };

    /**
     * Returns a byte array containing an ICMPv6 echo request with the specified payload length.
     */
    private byte[] pingPacket(int payloadLength) {
        byte[] packet = new byte[payloadLength + 8];
        new Random().nextBytes(packet);
        System.arraycopy(PING_HEADER, 0, packet, 0, PING_HEADER.length);
        return packet;
    }

    /**
     * Checks that the first length bytes of two byte arrays are equal.
     */
    private void assertArrayBytesEqual(byte[] expected, byte[] actual, int length) {
        for (int i = 0; i < length; i++) {
            assertEquals("Arrays differ at index " + i + ":", expected[i], actual[i]);
        }
    }

    /**
     * Creates an IPv6 ping socket and sets a receive timeout of 100ms.
     */
    private FileDescriptor createPingSocket() throws ErrnoException {
        FileDescriptor s = Libcore.os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);
        Libcore.os.setsockoptTimeval(s, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(100));
        return s;
    }

    /**
     * Sends a ping packet to a random port on the specified address on the specified socket.
     */
    private void sendPing(FileDescriptor s,
            InetAddress address, byte[] packet) throws ErrnoException, IOException {
        // Pick a random port. Choose a range that gives a reasonable chance of picking a low port.
        int port = (int) (Math.random() * 2048);

        // Send the packet.
        int ret = Libcore.os.sendto(s, ByteBuffer.wrap(packet), 0, address, port);
        assertEquals(packet.length, ret);
    }

    /**
     * Checks that a socket has received a response appropriate to the specified packet.
     */
    private void checkResponse(FileDescriptor s, InetAddress dest,
            byte[] sent, boolean useRecvfrom) throws ErrnoException, IOException {
        ByteBuffer responseBuffer = ByteBuffer.allocate(MAX_SIZE);
        int bytesRead;

        // Receive the response.
        if (useRecvfrom) {
            InetSocketAddress from = new InetSocketAddress();
            bytesRead = Libcore.os.recvfrom(s, responseBuffer, 0, from);

            // Check the source address and scope ID.
            assertTrue(from.getAddress() instanceof Inet6Address);
            Inet6Address fromAddress = (Inet6Address) from.getAddress();
            assertEquals(0, fromAddress.getScopeId());
            assertNull(fromAddress.getScopedInterface());
            assertEquals(dest.getHostAddress(), fromAddress.getHostAddress());
        } else {
            bytesRead = Libcore.os.read(s, responseBuffer);
        }

        // Check the packet length.
        assertEquals(sent.length, bytesRead);

        // Check the response is an echo reply.
        byte[] response = new byte[bytesRead];
        responseBuffer.get(response, 0, bytesRead);
        assertEquals((byte) 0x81, response[0]);

        // Find out what ICMP ID was used in the packet that was sent.
        int id = ((InetSocketAddress) Libcore.os.getsockname(s)).getPort();
        sent[4] = (byte) (id / 256);
        sent[5] = (byte) (id % 256);

        // Ensure the response is the same as the packet, except for the type (which is 0x81)
        // and the ID and checksum,  which are set by the kernel.
        response[0] = (byte) 0x80;                 // Type.
        response[2] = response[3] = (byte) 0x00;   // Checksum.
        assertArrayBytesEqual(response, sent, bytesRead);
    }

    /**
     * Sends NUM_PACKETS random ping packets to ::1 and checks the replies.
     */
    public void testLoopbackPing() throws ErrnoException, IOException {
        // Generate a random ping packet and send it to localhost.
        InetAddress ipv6Loopback = InetAddress.getByName(null);
        assertEquals("localhost/::1", ipv6Loopback.toString());

        for (int i = 0; i < NUM_PACKETS; i++) {
            byte[] packet = pingPacket((int) (Math.random() * MAX_SIZE));
            FileDescriptor s = createPingSocket();
            // Use both recvfrom and read().
            sendPing(s, ipv6Loopback, packet);
            checkResponse(s, ipv6Loopback, packet, true);
            sendPing(s, ipv6Loopback, packet);
            checkResponse(s, ipv6Loopback, packet, false);
            // Check closing the socket doesn't raise an exception.
            Libcore.os.close(s);
        }
    }
}