Loading common/netlinkclient/src/android/net/netlink/StructNdOptPref64.java 0 → 100644 +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); } } src/com/android/server/util/NetworkStackConstants.java +1 −0 Original line number Diff line number Diff line Loading @@ -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; Loading tests/unit/src/android/net/netlink/StructNdOptPref64Test.java 0 → 100644 +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()); } } Loading
common/netlinkclient/src/android/net/netlink/StructNdOptPref64.java 0 → 100644 +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); } }
src/com/android/server/util/NetworkStackConstants.java +1 −0 Original line number Diff line number Diff line Loading @@ -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; Loading
tests/unit/src/android/net/netlink/StructNdOptPref64Test.java 0 → 100644 +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()); } }