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