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 android.net.netlink;
18 
19 import static android.system.OsConstants.AF_INET6;
20 
21 import androidx.annotation.NonNull;
22 
23 import java.net.Inet6Address;
24 import java.net.InetAddress;
25 import java.net.UnknownHostException;
26 import java.nio.BufferUnderflowException;
27 import java.nio.ByteBuffer;
28 import java.nio.ByteOrder;
29 
30 /**
31  * A NetlinkMessage subclass for RTM_NEWNDUSEROPT messages.
32  */
33 public class NduseroptMessage extends NetlinkMessage {
34     public static final int STRUCT_SIZE = 16;
35 
36     static final int NDUSEROPT_SRCADDR = 1;
37 
38     /** The address family. Presumably always AF_INET6. */
39     public final byte family;
40     /**
41      * The total length in bytes of the options that follow this structure.
42      * Actually a 16-bit unsigned integer.
43      */
44     public final int opts_len;
45     /** The interface index on which the options were received. */
46     public final int ifindex;
47     /** The ICMP type of the packet that contained the options. */
48     public final byte icmp_type;
49     /** The ICMP code of the packet that contained the options. */
50     public final byte icmp_code;
51 
52     /**
53      * ND option that was in this message.
54      * Even though the length field is called "opts_len", the kernel only ever sends one option per
55      * message. It is unlikely that this will ever change as it would break existing userspace code.
56      * But if it does, we can simply update this code, since userspace is typically newer than the
57      * kernel.
58      */
59     public final NdOption option;
60 
61     /** The IP address that sent the packet containing the option. */
62     public final InetAddress srcaddr;
63 
NduseroptMessage(@onNull StructNlMsgHdr header, @NonNull ByteBuffer buf)64     NduseroptMessage(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf)
65             throws UnknownHostException {
66         super(header);
67 
68         // The structure itself.
69         buf.order(ByteOrder.nativeOrder());  // Restored in the finally clause inside parse().
70         final int start = buf.position();
71         family = buf.get();
72         buf.get();  // Skip 1 byte of padding.
73         opts_len = Short.toUnsignedInt(buf.getShort());
74         ifindex = buf.getInt();
75         icmp_type = buf.get();
76         icmp_code = buf.get();
77         buf.position(buf.position() + 6);  // Skip 6 bytes of padding.
78 
79         // The ND option.
80         // Ensure we don't read past opts_len even if the option length is invalid.
81         // Note that this check is not really necessary since if the option length is not valid,
82         // this struct won't be very useful to the caller.
83         buf.order(ByteOrder.BIG_ENDIAN);
84         int oldLimit = buf.limit();
85         buf.limit(start + STRUCT_SIZE + opts_len);
86         try {
87             option = NdOption.parse(buf);
88         } finally {
89             buf.limit(oldLimit);
90         }
91 
92         // The source address.
93         int newPosition = start + STRUCT_SIZE + opts_len;
94         if (newPosition >= buf.limit()) {
95             throw new IllegalArgumentException("ND options extend past end of buffer");
96         }
97         buf.position(newPosition);
98 
99         StructNlAttr nla = StructNlAttr.parse(buf);
100         if (nla == null || nla.nla_type != NDUSEROPT_SRCADDR || nla.nla_value == null) {
101             throw new IllegalArgumentException("Invalid source address in ND useropt");
102         }
103         if (family == AF_INET6) {
104             // InetAddress.getByAddress only looks at the ifindex if the address type needs one.
105             srcaddr = Inet6Address.getByAddress(null /* hostname */, nla.nla_value, ifindex);
106         } else {
107             srcaddr = InetAddress.getByAddress(nla.nla_value);
108         }
109     }
110 
111     /**
112      * Parses a StructNduseroptmsg from a {@link ByteBuffer}.
113      *
114      * @param header the netlink message header.
115      * @param buf The buffer from which to parse the option. The buffer's byte order must be
116      *            {@link java.nio.ByteOrder#BIG_ENDIAN}.
117      * @return the parsed option, or {@code null} if the option could not be parsed successfully
118      *         (for example, if it was truncated, or if the prefix length code was wrong).
119      */
parse(@onNull StructNlMsgHdr header, @NonNull ByteBuffer buf)120     public static NduseroptMessage parse(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf) {
121         if (buf == null || buf.remaining() < STRUCT_SIZE) return null;
122         ByteOrder oldOrder = buf.order();
123         try {
124             return new NduseroptMessage(header, buf);
125         } catch (IllegalArgumentException | UnknownHostException | BufferUnderflowException e) {
126             // Not great, but better than throwing an exception that might crash the caller.
127             // Convention in this package is that null indicates that the option was truncated, so
128             // callers must already handle it.
129             return null;
130         } finally {
131             buf.order(oldOrder);
132         }
133     }
134 
135     @Override
toString()136     public String toString() {
137         return String.format("Nduseroptmsg(%d, %d, %d, %d, %d, %s)",
138                 family, opts_len, ifindex, Byte.toUnsignedInt(icmp_type),
139                 Byte.toUnsignedInt(icmp_code), srcaddr.getHostAddress());
140     }
141 }
142