| /* |
| * 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.net.module.util.netlink; |
| |
| import android.annotation.SuppressLint; |
| import android.net.IpPrefix; |
| import android.util.Log; |
| |
| import androidx.annotation.NonNull; |
| |
| import java.net.Inet6Address; |
| import java.net.InetAddress; |
| import java.net.UnknownHostException; |
| import java.nio.ByteBuffer; |
| import java.util.Objects; |
| |
| /** |
| * 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 extends NdOption { |
| public static final int STRUCT_SIZE = 16; |
| public static final int TYPE = 38; |
| public static final byte LENGTH = 2; |
| |
| private static final String TAG = StructNdOptPref64.class.getSimpleName(); |
| |
| /** |
| * 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. */ |
| @NonNull public final IpPrefix prefix; |
| |
| static 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); |
| } |
| } |
| |
| static int prefixLengthToPlc(int prefixLength) { |
| switch (prefixLength) { |
| case 96: return 0; |
| case 64: return 1; |
| case 56: return 2; |
| case 48: return 3; |
| case 40: return 4; |
| case 32: return 5; |
| default: |
| throw new IllegalArgumentException("Invalid prefix length " + prefixLength); |
| } |
| } |
| |
| /** |
| * Returns the 2-byte "scaled lifetime and prefix length code" field: 13-bit lifetime, 3-bit PLC |
| */ |
| static short getScaledLifetimePlc(int lifetime, int prefixLengthCode) { |
| return (short) ((lifetime & 0xfff8) | (prefixLengthCode & 0x7)); |
| } |
| |
| public StructNdOptPref64(@NonNull IpPrefix prefix, int lifetime) { |
| super((byte) TYPE, LENGTH); |
| |
| Objects.requireNonNull(prefix, "prefix must not be null"); |
| if (!(prefix.getAddress() instanceof Inet6Address)) { |
| throw new IllegalArgumentException("Must be an IPv6 prefix: " + prefix); |
| } |
| prefixLengthToPlc(prefix.getPrefixLength()); // Throw if the prefix length is invalid. |
| this.prefix = prefix; |
| |
| if (lifetime < 0 || lifetime > 0xfff8) { |
| throw new IllegalArgumentException("Invalid lifetime " + lifetime); |
| } |
| this.lifetime = lifetime & 0xfff8; |
| } |
| |
| @SuppressLint("NewApi") |
| private StructNdOptPref64(@NonNull ByteBuffer buf) { |
| super(buf.get(), Byte.toUnsignedInt(buf.get())); |
| if (type != TYPE) throw new IllegalArgumentException("Invalid type " + type); |
| if (length != LENGTH) 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.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; |
| } |
| } |
| |
| protected void writeToByteBuffer(ByteBuffer buf) { |
| super.writeToByteBuffer(buf); |
| buf.putShort(getScaledLifetimePlc(lifetime, prefixLengthToPlc(prefix.getPrefixLength()))); |
| buf.put(prefix.getRawAddress(), 0, 12); |
| } |
| |
| /** Outputs the wire format of the option to a new big-endian ByteBuffer. */ |
| public ByteBuffer toByteBuffer() { |
| ByteBuffer buf = ByteBuffer.allocate(STRUCT_SIZE); |
| writeToByteBuffer(buf); |
| buf.flip(); |
| return buf; |
| } |
| |
| @Override |
| @NonNull |
| public String toString() { |
| return String.format("NdOptPref64(%s, %d)", prefix, lifetime); |
| } |
| } |