blob: bdf574db89af911e7c907b3cf99df9c4c9f44c6c [file] [log] [blame]
/*
* 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 static android.system.OsConstants.AF_INET6;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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.
*/
@Nullable
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()); // Restored in the finally clause inside parse().
final int start = buf.position();
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.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.
//
// It's safer to pass the slice of original ByteBuffer to just parse the ND option field,
// although parsing ND option might throw exception or return null, it won't break the
// original ByteBuffer position.
buf.order(ByteOrder.BIG_ENDIAN);
try {
final ByteBuffer slice = buf.slice();
slice.limit(opts_len);
option = NdOption.parse(slice);
} finally {
// Advance buffer position according to opts_len in the header. ND option length might
// be incorrect in the malformed packet.
int newPosition = start + STRUCT_SIZE + opts_len;
if (newPosition >= buf.limit()) {
throw new IllegalArgumentException("ND option extends past end of buffer");
}
buf.position(newPosition);
}
// The source address attribute.
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).
*/
@Nullable
public static NduseroptMessage parse(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf) {
if (buf == null || buf.remaining() < STRUCT_SIZE) return null;
ByteOrder oldOrder = buf.order();
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;
} finally {
buf.order(oldOrder);
}
}
@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());
}
}