1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.net.module.util.netlink; 18 19 import static android.system.OsConstants.AF_INET6; 20 21 import androidx.annotation.NonNull; 22 import androidx.annotation.Nullable; 23 import androidx.annotation.VisibleForTesting; 24 25 import java.net.Inet6Address; 26 import java.net.InetAddress; 27 import java.net.UnknownHostException; 28 import java.nio.BufferUnderflowException; 29 import java.nio.ByteBuffer; 30 import java.nio.ByteOrder; 31 32 /** 33 * A NetlinkMessage subclass for RTM_NEWNDUSEROPT messages. 34 */ 35 public class NduseroptMessage extends NetlinkMessage { 36 public static final int STRUCT_SIZE = 16; 37 38 static final int NDUSEROPT_SRCADDR = 1; 39 40 /** The address family. Presumably always AF_INET6. */ 41 public final byte family; 42 /** 43 * The total length in bytes of the options that follow this structure. 44 * Actually a 16-bit unsigned integer. 45 */ 46 public final int opts_len; 47 /** The interface index on which the options were received. */ 48 public final int ifindex; 49 /** The ICMP type of the packet that contained the options. */ 50 public final byte icmp_type; 51 /** The ICMP code of the packet that contained the options. */ 52 public final byte icmp_code; 53 54 /** 55 * ND option that was in this message. 56 * Even though the length field is called "opts_len", the kernel only ever sends one option per 57 * message. It is unlikely that this will ever change as it would break existing userspace code. 58 * But if it does, we can simply update this code, since userspace is typically newer than the 59 * kernel. 60 */ 61 @Nullable 62 public final NdOption option; 63 64 /** The IP address that sent the packet containing the option. */ 65 public final InetAddress srcaddr; 66 67 @VisibleForTesting NduseroptMessage(@onNull final StructNlMsgHdr header, byte family, int optslen, int ifindex, byte icmptype, byte icmpcode, @NonNull final NdOption option, final InetAddress srcaddr)68 public NduseroptMessage(@NonNull final StructNlMsgHdr header, byte family, int optslen, 69 int ifindex, byte icmptype, byte icmpcode, @NonNull final NdOption option, 70 final InetAddress srcaddr) { 71 super(header); 72 this.family = family; 73 this.opts_len = optslen; 74 this.ifindex = ifindex; 75 this.icmp_type = icmptype; 76 this.icmp_code = icmpcode; 77 this.option = option; 78 this.srcaddr = srcaddr; 79 } 80 NduseroptMessage(@onNull StructNlMsgHdr header, @NonNull ByteBuffer buf)81 NduseroptMessage(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf) 82 throws UnknownHostException { 83 super(header); 84 85 // The structure itself. 86 buf.order(ByteOrder.nativeOrder()); // Restored in the finally clause inside parse(). 87 final int start = buf.position(); 88 family = buf.get(); 89 buf.get(); // Skip 1 byte of padding. 90 opts_len = Short.toUnsignedInt(buf.getShort()); 91 ifindex = buf.getInt(); 92 icmp_type = buf.get(); 93 icmp_code = buf.get(); 94 buf.position(buf.position() + 6); // Skip 6 bytes of padding. 95 96 // The ND option. 97 // Ensure we don't read past opts_len even if the option length is invalid. 98 // Note that this check is not really necessary since if the option length is not valid, 99 // this struct won't be very useful to the caller. 100 // 101 // It's safer to pass the slice of original ByteBuffer to just parse the ND option field, 102 // although parsing ND option might throw exception or return null, it won't break the 103 // original ByteBuffer position. 104 buf.order(ByteOrder.BIG_ENDIAN); 105 try { 106 final ByteBuffer slice = buf.slice(); 107 slice.limit(opts_len); 108 option = NdOption.parse(slice); 109 } finally { 110 // Advance buffer position according to opts_len in the header. ND option length might 111 // be incorrect in the malformed packet. 112 int newPosition = start + STRUCT_SIZE + opts_len; 113 if (newPosition >= buf.limit()) { 114 throw new IllegalArgumentException("ND option extends past end of buffer"); 115 } 116 buf.position(newPosition); 117 } 118 119 // The source address attribute. 120 StructNlAttr nla = StructNlAttr.parse(buf); 121 if (nla == null || nla.nla_type != NDUSEROPT_SRCADDR || nla.nla_value == null) { 122 throw new IllegalArgumentException("Invalid source address in ND useropt"); 123 } 124 if (family == AF_INET6) { 125 // InetAddress.getByAddress only looks at the ifindex if the address type needs one. 126 srcaddr = Inet6Address.getByAddress(null /* hostname */, nla.nla_value, ifindex); 127 } else { 128 srcaddr = InetAddress.getByAddress(nla.nla_value); 129 } 130 } 131 132 /** 133 * Parses a StructNduseroptmsg from a {@link ByteBuffer}. 134 * 135 * @param header the netlink message header. 136 * @param buf The buffer from which to parse the option. The buffer's byte order must be 137 * {@link java.nio.ByteOrder#BIG_ENDIAN}. 138 * @return the parsed option, or {@code null} if the option could not be parsed successfully 139 * (for example, if it was truncated, or if the prefix length code was wrong). 140 */ 141 @Nullable parse(@onNull StructNlMsgHdr header, @NonNull ByteBuffer buf)142 public static NduseroptMessage parse(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf) { 143 if (buf == null || buf.remaining() < STRUCT_SIZE) return null; 144 ByteOrder oldOrder = buf.order(); 145 try { 146 return new NduseroptMessage(header, buf); 147 } catch (IllegalArgumentException | UnknownHostException | BufferUnderflowException e) { 148 // Not great, but better than throwing an exception that might crash the caller. 149 // Convention in this package is that null indicates that the option was truncated, so 150 // callers must already handle it. 151 return null; 152 } finally { 153 buf.order(oldOrder); 154 } 155 } 156 157 @Override toString()158 public String toString() { 159 return String.format("Nduseroptmsg(family:%d, opts_len:%d, ifindex:%d, icmp_type:%d, " 160 + "icmp_code:%d, srcaddr: %s, %s)", 161 family, opts_len, ifindex, Byte.toUnsignedInt(icmp_type), 162 Byte.toUnsignedInt(icmp_code), srcaddr.getHostAddress(), option); 163 } 164 } 165