Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 109ee272 authored by Lorenzo Colitti's avatar Lorenzo Colitti Committed by Android (Google) Code Review
Browse files

Merge "Support decoding the new PREF64 RA option." into rvc-dev

parents 044be5f2 d6cc960f
Loading
Loading
Loading
Loading
+121 −0
Original line number Diff line number Diff line
/*
 * 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.netlink;

import android.net.IpPrefix;
import android.util.Log;

import androidx.annotation.NonNull;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;

/**
 * The PREF64 router advertisement option. RFC 8781.
 *
 * 0                   1                   2                   3
 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |     Type      |    Length     |     Scaled Lifetime     | PLC |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                                                               |
 * +                                                               +
 * |              Highest 96 bits of the Prefix                    |
 * +                                                               +
 * |                                                               |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *
 */
public class StructNdOptPref64 {
    public static final int STRUCT_SIZE = 16;
    public static final int TYPE = 38;

    private static final String TAG = StructNdOptPref64.class.getSimpleName();

    /** The option type. Always ICMPV6_ND_OPTION_PREF64. */
    public final byte type;
    /** The length of the option in 8-byte units. Actually an unsigned 8-bit integer. */
    public final int length;
    /**
     * How many seconds the prefix is expected to remain valid.
     * Valid values are from 0 to 65528 in multiples of 8.
     */
    public final int lifetime;
    /** The NAT64 prefix. */
    public final IpPrefix prefix;

    int plcToPrefixLength(int plc) {
        switch (plc) {
            case 0: return 96;
            case 1: return 64;
            case 2: return 56;
            case 3: return 48;
            case 4: return 40;
            case 5: return 32;
            default:
                throw new IllegalArgumentException("Invalid prefix length code " + plc);
        }
    }

    StructNdOptPref64(@NonNull ByteBuffer buf) {
        type = buf.get();
        length = buf.get();
        if (type != TYPE) throw new IllegalArgumentException("Invalid type " + type);
        if (length != 2) throw new IllegalArgumentException("Invalid length " + length);

        int scaledLifetimePlc = Short.toUnsignedInt(buf.getShort());
        lifetime = scaledLifetimePlc & 0xfff8;

        byte[] addressBytes = new byte[16];
        buf.get(addressBytes, 0, 12);
        InetAddress addr;
        try {
            addr = InetAddress.getByAddress(addressBytes);
        } catch (UnknownHostException e) {
            throw new AssertionError("16-byte array not valid InetAddress?");
        }
        prefix = new IpPrefix(addr, plcToPrefixLength(scaledLifetimePlc & 7));
    }

    /**
     * Parses an option from a {@link ByteBuffer}.
     *
     * @param buf The buffer from which to parse the option. The buffer's byte order must be
     *            {@link java.nio.ByteOrder#BIG_ENDIAN}.
     * @return the parsed option, or {@code null} if the option could not be parsed successfully
     *         (for example, if it was truncated, or if the prefix length code was wrong).
     */
    public static StructNdOptPref64 parse(@NonNull ByteBuffer buf) {
        if (buf == null || buf.remaining() < STRUCT_SIZE) return null;
        try {
            return new StructNdOptPref64(buf);
        } catch (IllegalArgumentException e) {
            // Not great, but better than throwing an exception that might crash the caller.
            // Convention in this package is that null indicates that the option was truncated, so
            // callers must already handle it.
            Log.d(TAG, "Invalid PREF64 option: " + e);
            return null;
        }
    }

    @Override
    @NonNull
    public String toString() {
        return String.format("NdOptPref64(%s, %d)", prefix, lifetime);
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -126,6 +126,7 @@ public final class NetworkStackConstants {
    public static final int ICMPV6_ND_OPTION_PIO   = 3;
    public static final int ICMPV6_ND_OPTION_MTU   = 5;
    public static final int ICMPV6_ND_OPTION_RDNSS = 25;
    public static final int ICMPV6_ND_OPTION_PREF64 = 38;


    public static final int ICMPV6_RA_HEADER_LEN = 16;
+155 −0
Original line number Diff line number Diff line
/*
 * 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.netlink;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

import android.net.IpPrefix;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import libcore.util.HexEncoding;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.net.InetAddress;
import java.nio.ByteBuffer;

@RunWith(AndroidJUnit4.class)
@SmallTest
public class StructNdOptPref64Test {

    private static final String PREFIX1 = "64:ff9b::";
    private static final String PREFIX2 = "2001:db8:1:2:3:64::";

    private static byte[] prefixBytes(String addrString) throws Exception {
        InetAddress addr = InetAddress.getByName(addrString);
        byte[] prefixBytes = new byte[12];
        System.arraycopy(addr.getAddress(), 0, prefixBytes, 0, 12);
        return prefixBytes;
    }

    private static IpPrefix prefix(String addrString, int prefixLength) throws Exception {
        return new IpPrefix(InetAddress.getByName(addrString), prefixLength);
    }

    private void assertPref64OptMatches(int lifetime, IpPrefix prefix, StructNdOptPref64 opt) {
        assertEquals(StructNdOptPref64.TYPE, opt.type);
        assertEquals(2, opt.length);
        assertEquals(lifetime, opt.lifetime);
        assertEquals(prefix, opt.prefix);
    }

    /**
     * Returns the 2-byte "scaled lifetime and prefix length code" field: 13-bit lifetime, 3-bit PLC
     */
    private short getPref64ScaledLifetimePlc(int lifetime, int prefixLengthCode) {
        return (short) ((lifetime & 0xfff8) | (prefixLengthCode & 0x7));
    }

    private ByteBuffer makeNdOptPref64(int lifetime, byte[] prefix, int prefixLengthCode) {
        if (prefix.length != 12) throw new IllegalArgumentException("Prefix must be 12 bytes");

        ByteBuffer buf = ByteBuffer.allocate(16)
                .put((byte) StructNdOptPref64.TYPE)
                .put((byte) 2)  // len=2 (16 bytes)
                .putShort(getPref64ScaledLifetimePlc(lifetime, prefixLengthCode))
                .put(prefix, 0, 12);

        buf.flip();
        return buf;
    }

    @Test
    public void testParseCannedOption() throws Exception {
        String hexBytes = "2602"               // type=38, len=2 (16 bytes)
                + "0088"                       // lifetime=136, PLC=0 (/96)
                + "20010db80003000400050006";  // 2001:db8:3:4:5:6/96
        byte[] rawBytes = HexEncoding.decode(hexBytes);
        StructNdOptPref64 opt = StructNdOptPref64.parse(ByteBuffer.wrap(rawBytes));
        assertPref64OptMatches(136, prefix("2001:db8:3:4:5:6::", 96), opt);

        hexBytes = "2602"                      // type=38, len=2 (16 bytes)
                + "2752"                       // lifetime=10064, PLC=2 (/56)
                + "0064ff9b0000000000000000";  // 64:ff9b::/56
        rawBytes = HexEncoding.decode(hexBytes);
        opt = StructNdOptPref64.parse(ByteBuffer.wrap(rawBytes));
        assertPref64OptMatches(10064, prefix("64:ff9b::", 56), opt);
    }

    @Test
    public void testParsing() throws Exception {
        // Valid.
        ByteBuffer buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 0);
        StructNdOptPref64 opt = StructNdOptPref64.parse(buf);
        assertPref64OptMatches(600, prefix(PREFIX1, 96), opt);

        // Valid, zero lifetime, /64.
        buf = makeNdOptPref64(0, prefixBytes(PREFIX1), 1);
        opt = StructNdOptPref64.parse(buf);
        assertPref64OptMatches(0, prefix(PREFIX1, 64), opt);

        // Valid, low lifetime, /56.
        buf = makeNdOptPref64(8, prefixBytes(PREFIX2), 2);
        opt = StructNdOptPref64.parse(buf);
        assertPref64OptMatches(8, prefix(PREFIX2, 56), opt);
        assertEquals(new IpPrefix("2001:db8:1::/56"), opt.prefix);  // Prefix is truncated.

        // Valid, maximum lifetime, /32.
        buf = makeNdOptPref64(65528, prefixBytes(PREFIX2), 5);
        opt = StructNdOptPref64.parse(buf);
        assertPref64OptMatches(65528, prefix(PREFIX2, 32), opt);
        assertEquals(new IpPrefix("2001:db8::/32"), opt.prefix);  // Prefix is truncated.

        // Lifetime not divisible by 8.
        buf = makeNdOptPref64(300, prefixBytes(PREFIX2), 0);
        opt = StructNdOptPref64.parse(buf);
        assertPref64OptMatches(296, prefix(PREFIX2, 96), opt);

        // Invalid prefix length codes.
        buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 6);
        assertNull(StructNdOptPref64.parse(buf));
        buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 7);
        assertNull(StructNdOptPref64.parse(buf));

        // Truncated to varying lengths...
        buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 3);
        final int len = buf.limit();
        for (int i = 0; i < buf.limit() - 1; i++) {
            buf.flip();
            buf.limit(i);
            assertNull("Option truncated to " + i + " bytes, should have returned null",
                    StructNdOptPref64.parse(buf));
        }
        buf.flip();
        buf.limit(len);
        // ... but otherwise OK.
        opt = StructNdOptPref64.parse(buf);
        assertPref64OptMatches(600, prefix(PREFIX1, 48), opt);
    }

    @Test
    public void testToString() throws Exception {
        ByteBuffer buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 4);
        StructNdOptPref64 opt = StructNdOptPref64.parse(buf);
        assertPref64OptMatches(600, prefix(PREFIX1, 40), opt);
        assertEquals("NdOptPref64(64:ff9b::/40, 600)", opt.toString());
    }
}