Loading common/netlinkclient/src/android/net/netlink/NdOption.java 0 → 100644 +78 −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 java.nio.ByteBuffer; /** * Base class for IPv6 neighbour discovery options. */ public class NdOption { public static final int STRUCT_SIZE = 2; /** The option type. */ public final byte type; /** The length of the option in 8-byte units. Actually an unsigned 8-bit integer */ public final int length; /** Constructs a new NdOption. */ public NdOption(byte type, int length) { this.type = type; this.length = length; } /** * Parses a neighbour discovery option. * * Parses (and consumes) the option if it is of a known type. If the option is of an unknown * type, advances the buffer (so the caller can continue parsing if desired) and returns * {@link #UNKNOWN}. If the option claims a length of 0, returns null because parsing cannot * continue. * * No checks are performed on the length other than ensuring it is not 0, so if a caller wants * to deal with options that might overflow the structure that contains them, it must explicitly * set the buffer's limit to the position at which that structure ends. * * @param buf the buffer to parse. * @return a subclass of {@link NdOption}, or {@code null} for an unknown or malformed option. */ public static NdOption parse(ByteBuffer buf) { if (buf == null || buf.remaining() < STRUCT_SIZE) return null; // Peek the type without advancing the buffer. byte type = buf.get(buf.position()); int length = Byte.toUnsignedInt(buf.get(buf.position() + 1)); if (length == 0) return null; switch (type) { case StructNdOptPref64.TYPE: return StructNdOptPref64.parse(buf); default: int newPosition = Math.min(buf.limit(), buf.position() + length * 8); buf.position(newPosition); return UNKNOWN; } } @Override public String toString() { return String.format("NdOption(%d, %d)", Byte.toUnsignedInt(type), length); } public static final NdOption UNKNOWN = new NdOption((byte) 0, 0); } common/netlinkclient/src/android/net/netlink/NduseroptMessage.java 0 → 100644 +137 −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 static android.system.OsConstants.AF_INET6; import androidx.annotation.NonNull; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; /** * A NetlinkMessage subclass for RTM_NEWNDUSEROPT messages. */ public class NduseroptMessage extends NetlinkMessage { public static final int STRUCT_SIZE = 16; static final int NDUSEROPT_SRCADDR = 1; /** The address family. Presumably always AF_INET6. */ public final byte family; /** * The total length in bytes of the options that follow this structure. * Actually a 16-bit unsigned integer. */ public final int opts_len; /** The interface index on which the options were received. */ public final int ifindex; /** The ICMP type of the packet that contained the options. */ public final byte icmp_type; /** The ICMP code of the packet that contained the options. */ public final byte icmp_code; /** * ND option that was in this message. * Even though the length field is called "opts_len", the kernel only ever sends one option per * message. It is unlikely that this will ever change as it would break existing userspace code. * But if it does, we can simply update this code, since userspace is typically newer than the * kernel. */ public final NdOption option; /** The IP address that sent the packet containing the option. */ public final InetAddress srcaddr; NduseroptMessage(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf) throws UnknownHostException { super(header); // The structure itself. buf.order(ByteOrder.nativeOrder()); family = buf.get(); buf.get(); // Skip 1 byte of padding. opts_len = Short.toUnsignedInt(buf.getShort()); ifindex = buf.getInt(); icmp_type = buf.get(); icmp_code = buf.get(); buf.order(ByteOrder.BIG_ENDIAN); buf.position(buf.position() + 6); // Skip 6 bytes of padding. // The ND option. // Ensure we don't read past opts_len even if the option length is invalid. // Note that this check is not really necessary since if the option length is not valid, // this struct won't be very useful to the caller. int oldLimit = buf.limit(); buf.limit(STRUCT_SIZE + opts_len); try { option = NdOption.parse(buf); } finally { buf.limit(oldLimit); } // The source address. int newPosition = STRUCT_SIZE + opts_len; if (newPosition >= buf.limit()) { throw new IllegalArgumentException("ND options extend past end of buffer"); } buf.position(newPosition); StructNlAttr nla = StructNlAttr.parse(buf); if (nla == null || nla.nla_type != NDUSEROPT_SRCADDR || nla.nla_value == null) { throw new IllegalArgumentException("Invalid source address in ND useropt"); } if (family == AF_INET6) { // InetAddress.getByAddress only looks at the ifindex if the address type needs one. srcaddr = Inet6Address.getByAddress(null /* hostname */, nla.nla_value, ifindex); } else { srcaddr = InetAddress.getByAddress(nla.nla_value); } } /** * Parses a StructNduseroptmsg from a {@link ByteBuffer}. * * @param header the netlink message header. * @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 NduseroptMessage parse(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf) { if (buf == null || buf.remaining() < STRUCT_SIZE) return null; try { return new NduseroptMessage(header, buf); } catch (IllegalArgumentException | UnknownHostException | BufferUnderflowException 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. return null; } } @Override public String toString() { return String.format("Nduseroptmsg(%d, %d, %d, %d, %d, %s)", family, opts_len, ifindex, Byte.toUnsignedInt(icmp_type), Byte.toUnsignedInt(icmp_code), srcaddr.getHostAddress()); } } common/netlinkclient/src/android/net/netlink/NetlinkMessage.java +2 −0 Original line number Diff line number Diff line Loading @@ -64,6 +64,8 @@ public class NetlinkMessage { return (NetlinkMessage) RtNetlinkNeighborMessage.parse(nlmsghdr, byteBuffer); case NetlinkConstants.SOCK_DIAG_BY_FAMILY: return (NetlinkMessage) InetDiagMessage.parse(nlmsghdr, byteBuffer); case NetlinkConstants.RTM_NEWNDUSEROPT: return (NetlinkMessage) NduseroptMessage.parse(nlmsghdr, byteBuffer); default: if (nlmsghdr.nlmsg_type <= NetlinkConstants.NLMSG_MAX_RESERVED) { // Netlink control message. Just parse the header for now, Loading common/netlinkclient/src/android/net/netlink/StructNdOptPref64.java +3 −8 Original line number Diff line number Diff line Loading @@ -41,16 +41,12 @@ import java.nio.ByteBuffer; * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * */ public class StructNdOptPref64 { public class StructNdOptPref64 extends NdOption { 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. Loading @@ -72,9 +68,8 @@ public class StructNdOptPref64 { } } StructNdOptPref64(@NonNull ByteBuffer buf) { type = buf.get(); length = buf.get(); public StructNdOptPref64(@NonNull ByteBuffer buf) { super(buf.get(), Byte.toUnsignedInt(buf.get())); if (type != TYPE) throw new IllegalArgumentException("Invalid type " + type); if (length != 2) throw new IllegalArgumentException("Invalid length " + length); Loading tests/unit/src/android/net/netlink/NduseroptMessageTest.java 0 → 100644 +188 −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 static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; 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.nio.ByteBuffer; @RunWith(AndroidJUnit4.class) @SmallTest public class NduseroptMessageTest { // Pick ifindices that are high enough that they will "never" be an existing interface index, // and always be represented numerically in the address. That way, the test will never need to // determine the interface names corresponding to these indices. That simplifies the code and // makes the test more useful because determining interface names might require permissions. private static final int IFINDEX1 = 15715755; private static final int IFINDEX2 = 1431655765; // IPv6, 0 bytes of options, interface index 15715755, type 134 (RA), code 0, padding. private static final String HDR_EMPTY = "0a00" + "0000" + "abcdef00" + "8600000000000000"; // IPv6, 16 bytes of options, interface index 1431655765, type 134 (RA), code 0, padding. private static final String HDR_16BYTE = "0a00" + "1000" + "55555555" + "8600000000000000"; // IPv6, 32 bytes of options, interface index 1431655765, type 134 (RA), code 0, padding. private static final String HDR_32BYTE = "0a00" + "2000" + "55555555" + "8600000000000000"; // PREF64 option, 2001:db8:3:4:5:6::/96, lifetime=10064 private static final String OPT_PREF64 = "2602" + "2750" + "20010db80003000400050006"; // Length 20, NDUSEROPT_SRCADDR, fe80:2:3:4:5:6:7:8 private static final String NLA_SRCADDR = "1400" + "0100" + "fe800002000300040005000600070008"; private static final String SRCADDR1 = "fe80:2:3:4:5:6:7:8%" + IFINDEX1; private static final String SRCADDR2 = "fe80:2:3:4:5:6:7:8%" + IFINDEX2; private static final String MSG_EMPTY = HDR_EMPTY + NLA_SRCADDR; private static final String MSG_PREF64 = HDR_16BYTE + OPT_PREF64 + NLA_SRCADDR; @Test public void testParsing() { NduseroptMessage msg = parseNduseroptMessage(toBuffer(MSG_EMPTY)); assertMatches((byte) 10, 0, IFINDEX1, (byte) 134, (byte) 0, SRCADDR1, msg); assertNull(msg.option); msg = parseNduseroptMessage(toBuffer(MSG_PREF64)); assertMatches((byte) 10, 16, IFINDEX2, (byte) 134, (byte) 0, SRCADDR2, msg); assertPref64Option("2001:db8:3:4:5:6::/96", msg.option); } @Test public void testUnknownOption() { ByteBuffer buf = toBuffer(MSG_PREF64); // Replace the PREF64 option type (38) with an unknown option number. final int optionStart = NduseroptMessage.STRUCT_SIZE; assertEquals(38, buf.get(optionStart)); buf.put(optionStart, (byte) 42); NduseroptMessage msg = parseNduseroptMessage(buf); assertMatches((byte) 10, 16, IFINDEX2, (byte) 134, (byte) 0, SRCADDR2, msg); assertEquals(NdOption.UNKNOWN, msg.option); buf.flip(); assertEquals(42, buf.get(optionStart)); buf.put(optionStart, (byte) 38); msg = parseNduseroptMessage(buf); assertMatches((byte) 10, 16, IFINDEX2, (byte) 134, (byte) 0, SRCADDR2, msg); assertPref64Option("2001:db8:3:4:5:6::/96", msg.option); } @Test public void testZeroLengthOption() { // Make sure an unknown option with a 0-byte length is ignored and parsing continues with // the address, which comes after it. final String hexString = HDR_16BYTE + "00000000000000000000000000000000" + NLA_SRCADDR; ByteBuffer buf = toBuffer(hexString); assertEquals(52, buf.limit()); NduseroptMessage msg = parseNduseroptMessage(buf); assertMatches((byte) 10, 16, IFINDEX2, (byte) 134, (byte) 0, SRCADDR2, msg); assertNull(msg.option); } @Test public void testTooLongOption() { // Make sure that if an option's length is too long, it's ignored and parsing continues with // the address, which comes after it. final String hexString = HDR_16BYTE + "26030000000000000000000000000000" + NLA_SRCADDR; ByteBuffer buf = toBuffer(hexString); assertEquals(52, buf.limit()); NduseroptMessage msg = parseNduseroptMessage(buf); assertMatches((byte) 10, 16, IFINDEX2, (byte) 134, (byte) 0, SRCADDR2, msg); assertNull(msg.option); } @Test public void testOptionsTooLong() { // Header claims 32 bytes of options. Buffer ends before options end. String hexString = HDR_32BYTE + OPT_PREF64; ByteBuffer buf = toBuffer(hexString); assertEquals(32, buf.limit()); assertNull(NduseroptMessage.parse(toBuffer(hexString))); // Header claims 32 bytes of options. Buffer ends at end of options with no source address. hexString = HDR_32BYTE + OPT_PREF64 + OPT_PREF64; buf = toBuffer(hexString); assertEquals(48, buf.limit()); assertNull(NduseroptMessage.parse(toBuffer(hexString))); } @Test public void testTruncation() { final int optLen = MSG_PREF64.length() / 2; // 1 byte = 2 hex chars for (int len = 0; len < optLen; len++) { ByteBuffer buf = toBuffer(MSG_PREF64.substring(0, len * 2)); NduseroptMessage msg = parseNduseroptMessage(buf); if (len < optLen) { assertNull(msg); } else { assertNotNull(msg); assertPref64Option("2001:db8:3:4:5:6::/96", msg.option); } } } @Test public void testToString() { NduseroptMessage msg = parseNduseroptMessage(toBuffer(MSG_PREF64)); assertNotNull(msg); assertEquals("Nduseroptmsg(10, 16, 1431655765, 134, 0, fe80:2:3:4:5:6:7:8%1431655765)", msg.toString()); } // Convenience method to parse a NduseroptMessage that's not part of a netlink message. private NduseroptMessage parseNduseroptMessage(ByteBuffer buf) { return NduseroptMessage.parse(null, buf); } private ByteBuffer toBuffer(String hexString) { return ByteBuffer.wrap(HexEncoding.decode(hexString)); } private void assertMatches(byte family, int optsLen, int ifindex, byte icmpType, byte icmpCode, String srcaddr, NduseroptMessage msg) { assertNotNull(msg); assertEquals(family, msg.family); assertEquals(ifindex, msg.ifindex); assertEquals(optsLen, msg.opts_len); assertEquals(icmpType, msg.icmp_type); assertEquals(icmpCode, msg.icmp_code); assertEquals(srcaddr, msg.srcaddr.getHostAddress()); } private void assertPref64Option(String prefix, NdOption opt) { assertNotNull(opt); assertTrue(opt instanceof StructNdOptPref64); StructNdOptPref64 pref64Opt = (StructNdOptPref64) opt; assertEquals(new IpPrefix(prefix), pref64Opt.prefix); } } Loading
common/netlinkclient/src/android/net/netlink/NdOption.java 0 → 100644 +78 −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 java.nio.ByteBuffer; /** * Base class for IPv6 neighbour discovery options. */ public class NdOption { public static final int STRUCT_SIZE = 2; /** The option type. */ public final byte type; /** The length of the option in 8-byte units. Actually an unsigned 8-bit integer */ public final int length; /** Constructs a new NdOption. */ public NdOption(byte type, int length) { this.type = type; this.length = length; } /** * Parses a neighbour discovery option. * * Parses (and consumes) the option if it is of a known type. If the option is of an unknown * type, advances the buffer (so the caller can continue parsing if desired) and returns * {@link #UNKNOWN}. If the option claims a length of 0, returns null because parsing cannot * continue. * * No checks are performed on the length other than ensuring it is not 0, so if a caller wants * to deal with options that might overflow the structure that contains them, it must explicitly * set the buffer's limit to the position at which that structure ends. * * @param buf the buffer to parse. * @return a subclass of {@link NdOption}, or {@code null} for an unknown or malformed option. */ public static NdOption parse(ByteBuffer buf) { if (buf == null || buf.remaining() < STRUCT_SIZE) return null; // Peek the type without advancing the buffer. byte type = buf.get(buf.position()); int length = Byte.toUnsignedInt(buf.get(buf.position() + 1)); if (length == 0) return null; switch (type) { case StructNdOptPref64.TYPE: return StructNdOptPref64.parse(buf); default: int newPosition = Math.min(buf.limit(), buf.position() + length * 8); buf.position(newPosition); return UNKNOWN; } } @Override public String toString() { return String.format("NdOption(%d, %d)", Byte.toUnsignedInt(type), length); } public static final NdOption UNKNOWN = new NdOption((byte) 0, 0); }
common/netlinkclient/src/android/net/netlink/NduseroptMessage.java 0 → 100644 +137 −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 static android.system.OsConstants.AF_INET6; import androidx.annotation.NonNull; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; /** * A NetlinkMessage subclass for RTM_NEWNDUSEROPT messages. */ public class NduseroptMessage extends NetlinkMessage { public static final int STRUCT_SIZE = 16; static final int NDUSEROPT_SRCADDR = 1; /** The address family. Presumably always AF_INET6. */ public final byte family; /** * The total length in bytes of the options that follow this structure. * Actually a 16-bit unsigned integer. */ public final int opts_len; /** The interface index on which the options were received. */ public final int ifindex; /** The ICMP type of the packet that contained the options. */ public final byte icmp_type; /** The ICMP code of the packet that contained the options. */ public final byte icmp_code; /** * ND option that was in this message. * Even though the length field is called "opts_len", the kernel only ever sends one option per * message. It is unlikely that this will ever change as it would break existing userspace code. * But if it does, we can simply update this code, since userspace is typically newer than the * kernel. */ public final NdOption option; /** The IP address that sent the packet containing the option. */ public final InetAddress srcaddr; NduseroptMessage(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf) throws UnknownHostException { super(header); // The structure itself. buf.order(ByteOrder.nativeOrder()); family = buf.get(); buf.get(); // Skip 1 byte of padding. opts_len = Short.toUnsignedInt(buf.getShort()); ifindex = buf.getInt(); icmp_type = buf.get(); icmp_code = buf.get(); buf.order(ByteOrder.BIG_ENDIAN); buf.position(buf.position() + 6); // Skip 6 bytes of padding. // The ND option. // Ensure we don't read past opts_len even if the option length is invalid. // Note that this check is not really necessary since if the option length is not valid, // this struct won't be very useful to the caller. int oldLimit = buf.limit(); buf.limit(STRUCT_SIZE + opts_len); try { option = NdOption.parse(buf); } finally { buf.limit(oldLimit); } // The source address. int newPosition = STRUCT_SIZE + opts_len; if (newPosition >= buf.limit()) { throw new IllegalArgumentException("ND options extend past end of buffer"); } buf.position(newPosition); StructNlAttr nla = StructNlAttr.parse(buf); if (nla == null || nla.nla_type != NDUSEROPT_SRCADDR || nla.nla_value == null) { throw new IllegalArgumentException("Invalid source address in ND useropt"); } if (family == AF_INET6) { // InetAddress.getByAddress only looks at the ifindex if the address type needs one. srcaddr = Inet6Address.getByAddress(null /* hostname */, nla.nla_value, ifindex); } else { srcaddr = InetAddress.getByAddress(nla.nla_value); } } /** * Parses a StructNduseroptmsg from a {@link ByteBuffer}. * * @param header the netlink message header. * @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 NduseroptMessage parse(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf) { if (buf == null || buf.remaining() < STRUCT_SIZE) return null; try { return new NduseroptMessage(header, buf); } catch (IllegalArgumentException | UnknownHostException | BufferUnderflowException 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. return null; } } @Override public String toString() { return String.format("Nduseroptmsg(%d, %d, %d, %d, %d, %s)", family, opts_len, ifindex, Byte.toUnsignedInt(icmp_type), Byte.toUnsignedInt(icmp_code), srcaddr.getHostAddress()); } }
common/netlinkclient/src/android/net/netlink/NetlinkMessage.java +2 −0 Original line number Diff line number Diff line Loading @@ -64,6 +64,8 @@ public class NetlinkMessage { return (NetlinkMessage) RtNetlinkNeighborMessage.parse(nlmsghdr, byteBuffer); case NetlinkConstants.SOCK_DIAG_BY_FAMILY: return (NetlinkMessage) InetDiagMessage.parse(nlmsghdr, byteBuffer); case NetlinkConstants.RTM_NEWNDUSEROPT: return (NetlinkMessage) NduseroptMessage.parse(nlmsghdr, byteBuffer); default: if (nlmsghdr.nlmsg_type <= NetlinkConstants.NLMSG_MAX_RESERVED) { // Netlink control message. Just parse the header for now, Loading
common/netlinkclient/src/android/net/netlink/StructNdOptPref64.java +3 −8 Original line number Diff line number Diff line Loading @@ -41,16 +41,12 @@ import java.nio.ByteBuffer; * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * */ public class StructNdOptPref64 { public class StructNdOptPref64 extends NdOption { 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. Loading @@ -72,9 +68,8 @@ public class StructNdOptPref64 { } } StructNdOptPref64(@NonNull ByteBuffer buf) { type = buf.get(); length = buf.get(); public StructNdOptPref64(@NonNull ByteBuffer buf) { super(buf.get(), Byte.toUnsignedInt(buf.get())); if (type != TYPE) throw new IllegalArgumentException("Invalid type " + type); if (length != 2) throw new IllegalArgumentException("Invalid length " + length); Loading
tests/unit/src/android/net/netlink/NduseroptMessageTest.java 0 → 100644 +188 −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 static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; 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.nio.ByteBuffer; @RunWith(AndroidJUnit4.class) @SmallTest public class NduseroptMessageTest { // Pick ifindices that are high enough that they will "never" be an existing interface index, // and always be represented numerically in the address. That way, the test will never need to // determine the interface names corresponding to these indices. That simplifies the code and // makes the test more useful because determining interface names might require permissions. private static final int IFINDEX1 = 15715755; private static final int IFINDEX2 = 1431655765; // IPv6, 0 bytes of options, interface index 15715755, type 134 (RA), code 0, padding. private static final String HDR_EMPTY = "0a00" + "0000" + "abcdef00" + "8600000000000000"; // IPv6, 16 bytes of options, interface index 1431655765, type 134 (RA), code 0, padding. private static final String HDR_16BYTE = "0a00" + "1000" + "55555555" + "8600000000000000"; // IPv6, 32 bytes of options, interface index 1431655765, type 134 (RA), code 0, padding. private static final String HDR_32BYTE = "0a00" + "2000" + "55555555" + "8600000000000000"; // PREF64 option, 2001:db8:3:4:5:6::/96, lifetime=10064 private static final String OPT_PREF64 = "2602" + "2750" + "20010db80003000400050006"; // Length 20, NDUSEROPT_SRCADDR, fe80:2:3:4:5:6:7:8 private static final String NLA_SRCADDR = "1400" + "0100" + "fe800002000300040005000600070008"; private static final String SRCADDR1 = "fe80:2:3:4:5:6:7:8%" + IFINDEX1; private static final String SRCADDR2 = "fe80:2:3:4:5:6:7:8%" + IFINDEX2; private static final String MSG_EMPTY = HDR_EMPTY + NLA_SRCADDR; private static final String MSG_PREF64 = HDR_16BYTE + OPT_PREF64 + NLA_SRCADDR; @Test public void testParsing() { NduseroptMessage msg = parseNduseroptMessage(toBuffer(MSG_EMPTY)); assertMatches((byte) 10, 0, IFINDEX1, (byte) 134, (byte) 0, SRCADDR1, msg); assertNull(msg.option); msg = parseNduseroptMessage(toBuffer(MSG_PREF64)); assertMatches((byte) 10, 16, IFINDEX2, (byte) 134, (byte) 0, SRCADDR2, msg); assertPref64Option("2001:db8:3:4:5:6::/96", msg.option); } @Test public void testUnknownOption() { ByteBuffer buf = toBuffer(MSG_PREF64); // Replace the PREF64 option type (38) with an unknown option number. final int optionStart = NduseroptMessage.STRUCT_SIZE; assertEquals(38, buf.get(optionStart)); buf.put(optionStart, (byte) 42); NduseroptMessage msg = parseNduseroptMessage(buf); assertMatches((byte) 10, 16, IFINDEX2, (byte) 134, (byte) 0, SRCADDR2, msg); assertEquals(NdOption.UNKNOWN, msg.option); buf.flip(); assertEquals(42, buf.get(optionStart)); buf.put(optionStart, (byte) 38); msg = parseNduseroptMessage(buf); assertMatches((byte) 10, 16, IFINDEX2, (byte) 134, (byte) 0, SRCADDR2, msg); assertPref64Option("2001:db8:3:4:5:6::/96", msg.option); } @Test public void testZeroLengthOption() { // Make sure an unknown option with a 0-byte length is ignored and parsing continues with // the address, which comes after it. final String hexString = HDR_16BYTE + "00000000000000000000000000000000" + NLA_SRCADDR; ByteBuffer buf = toBuffer(hexString); assertEquals(52, buf.limit()); NduseroptMessage msg = parseNduseroptMessage(buf); assertMatches((byte) 10, 16, IFINDEX2, (byte) 134, (byte) 0, SRCADDR2, msg); assertNull(msg.option); } @Test public void testTooLongOption() { // Make sure that if an option's length is too long, it's ignored and parsing continues with // the address, which comes after it. final String hexString = HDR_16BYTE + "26030000000000000000000000000000" + NLA_SRCADDR; ByteBuffer buf = toBuffer(hexString); assertEquals(52, buf.limit()); NduseroptMessage msg = parseNduseroptMessage(buf); assertMatches((byte) 10, 16, IFINDEX2, (byte) 134, (byte) 0, SRCADDR2, msg); assertNull(msg.option); } @Test public void testOptionsTooLong() { // Header claims 32 bytes of options. Buffer ends before options end. String hexString = HDR_32BYTE + OPT_PREF64; ByteBuffer buf = toBuffer(hexString); assertEquals(32, buf.limit()); assertNull(NduseroptMessage.parse(toBuffer(hexString))); // Header claims 32 bytes of options. Buffer ends at end of options with no source address. hexString = HDR_32BYTE + OPT_PREF64 + OPT_PREF64; buf = toBuffer(hexString); assertEquals(48, buf.limit()); assertNull(NduseroptMessage.parse(toBuffer(hexString))); } @Test public void testTruncation() { final int optLen = MSG_PREF64.length() / 2; // 1 byte = 2 hex chars for (int len = 0; len < optLen; len++) { ByteBuffer buf = toBuffer(MSG_PREF64.substring(0, len * 2)); NduseroptMessage msg = parseNduseroptMessage(buf); if (len < optLen) { assertNull(msg); } else { assertNotNull(msg); assertPref64Option("2001:db8:3:4:5:6::/96", msg.option); } } } @Test public void testToString() { NduseroptMessage msg = parseNduseroptMessage(toBuffer(MSG_PREF64)); assertNotNull(msg); assertEquals("Nduseroptmsg(10, 16, 1431655765, 134, 0, fe80:2:3:4:5:6:7:8%1431655765)", msg.toString()); } // Convenience method to parse a NduseroptMessage that's not part of a netlink message. private NduseroptMessage parseNduseroptMessage(ByteBuffer buf) { return NduseroptMessage.parse(null, buf); } private ByteBuffer toBuffer(String hexString) { return ByteBuffer.wrap(HexEncoding.decode(hexString)); } private void assertMatches(byte family, int optsLen, int ifindex, byte icmpType, byte icmpCode, String srcaddr, NduseroptMessage msg) { assertNotNull(msg); assertEquals(family, msg.family); assertEquals(ifindex, msg.ifindex); assertEquals(optsLen, msg.opts_len); assertEquals(icmpType, msg.icmp_type); assertEquals(icmpCode, msg.icmp_code); assertEquals(srcaddr, msg.srcaddr.getHostAddress()); } private void assertPref64Option(String prefix, NdOption opt) { assertNotNull(opt); assertTrue(opt instanceof StructNdOptPref64); StructNdOptPref64 pref64Opt = (StructNdOptPref64) opt; assertEquals(new IpPrefix(prefix), pref64Opt.prefix); } }