diff options
3 files changed, 277 insertions, 0 deletions
diff --git a/common/netlinkclient/src/android/net/netlink/StructNdOptPref64.java b/common/netlinkclient/src/android/net/netlink/StructNdOptPref64.java new file mode 100644 index 0000000..6a68df8 --- /dev/null +++ b/common/netlinkclient/src/android/net/netlink/StructNdOptPref64.java @@ -0,0 +1,121 @@ +/* + * 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); + } +} diff --git a/src/com/android/server/util/NetworkStackConstants.java b/src/com/android/server/util/NetworkStackConstants.java index 660f0a6..dbba7f3 100644 --- a/src/com/android/server/util/NetworkStackConstants.java +++ b/src/com/android/server/util/NetworkStackConstants.java @@ -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; diff --git a/tests/unit/src/android/net/netlink/StructNdOptPref64Test.java b/tests/unit/src/android/net/netlink/StructNdOptPref64Test.java new file mode 100644 index 0000000..3d36d9b --- /dev/null +++ b/tests/unit/src/android/net/netlink/StructNdOptPref64Test.java @@ -0,0 +1,155 @@ +/* + * 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()); + } +} |