1 /*
2  * Copyright (C) 2022 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.net.util.SocketUtils.makeNetlinkSocketAddress;
20 import static android.system.OsConstants.AF_NETLINK;
21 import static android.system.OsConstants.EIO;
22 import static android.system.OsConstants.EPROTO;
23 import static android.system.OsConstants.ETIMEDOUT;
24 import static android.system.OsConstants.NETLINK_INET_DIAG;
25 import static android.system.OsConstants.NETLINK_ROUTE;
26 import static android.system.OsConstants.SOCK_CLOEXEC;
27 import static android.system.OsConstants.SOCK_DGRAM;
28 import static android.system.OsConstants.SOL_SOCKET;
29 import static android.system.OsConstants.SO_RCVBUF;
30 import static android.system.OsConstants.SO_RCVTIMEO;
31 import static android.system.OsConstants.SO_SNDTIMEO;
32 
33 import static com.android.net.module.util.netlink.NetlinkConstants.hexify;
34 import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE;
35 import static com.android.net.module.util.netlink.NetlinkConstants.RTNL_FAMILY_IP6MR;
36 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
37 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
38 
39 import android.net.util.SocketUtils;
40 import android.system.ErrnoException;
41 import android.system.Os;
42 import android.system.OsConstants;
43 import android.system.StructTimeval;
44 import android.util.Log;
45 
46 import androidx.annotation.NonNull;
47 import androidx.annotation.Nullable;
48 
49 import java.io.FileDescriptor;
50 import java.io.IOException;
51 import java.io.InterruptedIOException;
52 import java.net.Inet6Address;
53 import java.net.InetAddress;
54 import java.net.SocketException;
55 import java.nio.ByteBuffer;
56 import java.nio.ByteOrder;
57 import java.util.ArrayList;
58 import java.util.List;
59 import java.util.Objects;
60 import java.util.function.Consumer;
61 
62 /**
63  * Utilities for netlink related class that may not be able to fit into a specific class.
64  * @hide
65  */
66 public class NetlinkUtils {
67     private static final String TAG = "NetlinkUtils";
68     /** Corresponds to enum from bionic/libc/include/netinet/tcp.h. */
69     private static final int TCP_ESTABLISHED = 1;
70     private static final int TCP_SYN_SENT = 2;
71     private static final int TCP_SYN_RECV = 3;
72 
73     public static final int TCP_ALIVE_STATE_FILTER =
74             (1 << TCP_ESTABLISHED) | (1 << TCP_SYN_SENT) | (1 << TCP_SYN_RECV);
75 
76     public static final int UNKNOWN_MARK = 0xffffffff;
77     public static final int NULL_MASK = 0;
78 
79     // Initial mark value corresponds to the initValue in system/netd/include/Fwmark.h.
80     public static final int INIT_MARK_VALUE = 0;
81     // Corresponds to enum definition in bionic/libc/kernel/uapi/linux/inet_diag.h
82     public static final int INET_DIAG_INFO = 2;
83     public static final int INET_DIAG_MARK = 15;
84 
85     public static final long IO_TIMEOUT_MS = 300L;
86 
87     public static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
88     public static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
89     public static final int SOCKET_DUMP_RECV_BUFSIZE = 128 * 1024;
90 
91     /**
92      * Return whether the input ByteBuffer contains enough remaining bytes for
93      * {@code StructNlMsgHdr}.
94      */
enoughBytesRemainForValidNlMsg(@onNull final ByteBuffer bytes)95     public static boolean enoughBytesRemainForValidNlMsg(@NonNull final ByteBuffer bytes) {
96         return bytes.remaining() >= StructNlMsgHdr.STRUCT_SIZE;
97     }
98 
99     /**
100      * Parse netlink error message
101      *
102      * @param bytes byteBuffer to parse netlink error message
103      * @return NetlinkErrorMessage if bytes contains valid NetlinkErrorMessage, else {@code null}
104      */
105     @Nullable
parseNetlinkErrorMessage(ByteBuffer bytes)106     private static NetlinkErrorMessage parseNetlinkErrorMessage(ByteBuffer bytes) {
107         final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(bytes);
108         if (nlmsghdr == null || nlmsghdr.nlmsg_type != NetlinkConstants.NLMSG_ERROR) {
109             return null;
110         }
111 
112         final int messageLength = NetlinkConstants.alignedLengthOf(nlmsghdr.nlmsg_len);
113         final int payloadLength = messageLength - StructNlMsgHdr.STRUCT_SIZE;
114         if (payloadLength < 0 || payloadLength > bytes.remaining()) {
115             // Malformed message or runt buffer.  Pretend the buffer was consumed.
116             bytes.position(bytes.limit());
117             return null;
118         }
119 
120         return NetlinkErrorMessage.parse(nlmsghdr, bytes);
121     }
122 
123     /**
124      * Receive netlink ack message and check error
125      *
126      * @param fd fd to read netlink message
127      */
receiveNetlinkAck(final FileDescriptor fd)128     public static void receiveNetlinkAck(final FileDescriptor fd)
129             throws InterruptedIOException, ErrnoException {
130         final ByteBuffer bytes = recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT_MS);
131         // recvMessage() guaranteed to not return null if it did not throw.
132         final NetlinkErrorMessage response = parseNetlinkErrorMessage(bytes);
133         if (response != null && response.getNlMsgError() != null) {
134             final int errno = response.getNlMsgError().error;
135             if (errno != 0) {
136                 // TODO: consider ignoring EINVAL (-22), which appears to be
137                 // normal when probing a neighbor for which the kernel does
138                 // not already have / no longer has a link layer address.
139                 Log.e(TAG, "receiveNetlinkAck, errmsg=" + response.toString());
140                 // Note: convert kernel errnos (negative) into userspace errnos (positive).
141                 throw new ErrnoException(response.toString(), Math.abs(errno));
142             }
143         } else {
144             final String errmsg;
145             if (response == null) {
146                 bytes.position(0);
147                 errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
148             } else {
149                 errmsg = response.toString();
150             }
151             Log.e(TAG, "receiveNetlinkAck, errmsg=" + errmsg);
152             throw new ErrnoException(errmsg, EPROTO);
153         }
154     }
155 
156     /**
157      * Send one netlink message to kernel via netlink socket.
158      *
159      * @param nlProto netlink protocol type.
160      * @param msg the raw bytes of netlink message to be sent.
161      */
sendOneShotKernelMessage(int nlProto, byte[] msg)162     public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException {
163         final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage";
164         final FileDescriptor fd = netlinkSocketForProto(nlProto, SOCKET_RECV_BUFSIZE);
165 
166         try {
167             connectToKernel(fd);
168             sendMessage(fd, msg, 0, msg.length, IO_TIMEOUT_MS);
169             receiveNetlinkAck(fd);
170         } catch (InterruptedIOException e) {
171             Log.e(TAG, errPrefix, e);
172             throw new ErrnoException(errPrefix, ETIMEDOUT, e);
173         } catch (SocketException e) {
174             Log.e(TAG, errPrefix, e);
175             throw new ErrnoException(errPrefix, EIO, e);
176         } finally {
177             closeSocketQuietly(fd);
178         }
179     }
180 
181     /**
182      * Send an RTM_NEWADDR message to kernel to add or update an IP address.
183      *
184      * @param ifIndex interface index.
185      * @param ip IP address to be added.
186      * @param prefixlen IP address prefix length.
187      * @param flags IP address flags.
188      * @param scope IP address scope.
189      * @param preferred The preferred lifetime of IP address.
190      * @param valid The valid lifetime of IP address.
191      */
sendRtmNewAddressRequest(int ifIndex, @NonNull final InetAddress ip, short prefixlen, int flags, byte scope, long preferred, long valid)192     public static boolean sendRtmNewAddressRequest(int ifIndex, @NonNull final InetAddress ip,
193             short prefixlen, int flags, byte scope, long preferred, long valid) {
194         Objects.requireNonNull(ip, "IP address to be added should not be null.");
195         final byte[] msg = RtNetlinkAddressMessage.newRtmNewAddressMessage(1 /* seqNo*/, ip,
196                 prefixlen, flags, scope, ifIndex, preferred, valid);
197         try {
198             NetlinkUtils.sendOneShotKernelMessage(NETLINK_ROUTE, msg);
199             return true;
200         } catch (ErrnoException e) {
201             Log.e(TAG, "Fail to send RTM_NEWADDR to add " + ip.getHostAddress(), e);
202             return false;
203         }
204     }
205 
206     /**
207      * Send an RTM_DELADDR message to kernel to delete an IPv6 address.
208      *
209      * @param ifIndex interface index.
210      * @param ip IPv6 address to be deleted.
211      * @param prefixlen IPv6 address prefix length.
212      */
sendRtmDelAddressRequest(int ifIndex, final Inet6Address ip, short prefixlen)213     public static boolean sendRtmDelAddressRequest(int ifIndex, final Inet6Address ip,
214             short prefixlen) {
215         Objects.requireNonNull(ip, "IPv6 address to be deleted should not be null.");
216         final byte[] msg = RtNetlinkAddressMessage.newRtmDelAddressMessage(1 /* seqNo*/, ip,
217                 prefixlen, ifIndex);
218         try {
219             NetlinkUtils.sendOneShotKernelMessage(NETLINK_ROUTE, msg);
220             return true;
221         } catch (ErrnoException e) {
222             Log.e(TAG, "Fail to send RTM_DELADDR to delete " + ip.getHostAddress(), e);
223             return false;
224         }
225     }
226 
227     /**
228      * Create netlink socket with the given netlink protocol type and buffersize.
229      *
230      * @param nlProto the netlink protocol
231      * @param bufferSize the receive buffer size to set when the value is not 0
232      *
233      * @return fd the fileDescriptor of the socket.
234      * @throws ErrnoException if the FileDescriptor not connect to be created successfully
235      */
netlinkSocketForProto(int nlProto, int bufferSize)236     public static FileDescriptor netlinkSocketForProto(int nlProto, int bufferSize)
237             throws ErrnoException {
238         final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, nlProto);
239         if (bufferSize > 0) {
240             Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, bufferSize);
241         }
242         return fd;
243     }
244 
245     /**
246      * Create netlink socket with the given netlink protocol type. Receive buffer size is not set.
247      *
248      * @param nlProto the netlink protocol
249      *
250      * @return fd the fileDescriptor of the socket.
251      * @throws ErrnoException if the FileDescriptor not connect to be created successfully
252      */
netlinkSocketForProto(int nlProto)253     public static FileDescriptor netlinkSocketForProto(int nlProto)
254             throws ErrnoException {
255         return netlinkSocketForProto(nlProto, 0);
256     }
257 
258     /**
259      * Construct a netlink inet_diag socket.
260      */
createNetLinkInetDiagSocket()261     public static FileDescriptor createNetLinkInetDiagSocket() throws ErrnoException {
262         return netlinkSocketForProto(NETLINK_INET_DIAG);
263     }
264 
265     /**
266      * Connect the given file descriptor to the Netlink interface to the kernel.
267      *
268      * The fd must be a SOCK_DGRAM socket : create it with {@link #netlinkSocketForProto}
269      *
270      * @throws ErrnoException if the {@code fd} could not connect to kernel successfully
271      * @throws SocketException if there is an error accessing a socket.
272      */
connectToKernel(@onNull FileDescriptor fd)273     public static void connectToKernel(@NonNull FileDescriptor fd)
274             throws ErrnoException, SocketException {
275         Os.connect(fd, makeNetlinkSocketAddress(0, 0));
276     }
277 
checkTimeout(long timeoutMs)278     private static void checkTimeout(long timeoutMs) {
279         if (timeoutMs < 0) {
280             throw new IllegalArgumentException("Negative timeouts not permitted");
281         }
282     }
283 
284     /**
285      * Wait up to |timeoutMs| (or until underlying socket error) for a
286      * netlink message of at most |bufsize| size.
287      *
288      * Multi-threaded calls with different timeouts will cause unexpected results.
289      */
recvMessage(FileDescriptor fd, int bufsize, long timeoutMs)290     public static ByteBuffer recvMessage(FileDescriptor fd, int bufsize, long timeoutMs)
291             throws ErrnoException, IllegalArgumentException, InterruptedIOException {
292         checkTimeout(timeoutMs);
293 
294         Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(timeoutMs));
295 
296         final ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize);
297         final int length = Os.read(fd, byteBuffer);
298         if (length == bufsize) {
299             Log.w(TAG, "maximum read");
300         }
301         byteBuffer.position(0);
302         byteBuffer.limit(length);
303         byteBuffer.order(ByteOrder.nativeOrder());
304         return byteBuffer;
305     }
306 
307     /**
308      * Send a message to a peer to which this socket has previously connected.
309      *
310      * This waits at most |timeoutMs| milliseconds for the send to complete, will get the exception
311      * if it times out.
312      */
sendMessage( FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs)313     public static int sendMessage(
314             FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs)
315             throws ErrnoException, IllegalArgumentException, InterruptedIOException {
316         checkTimeout(timeoutMs);
317         Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(timeoutMs));
318         return Os.write(fd, bytes, offset, count);
319     }
320 
321     private static final long CLOCK_TICKS_PER_SECOND = Os.sysconf(OsConstants._SC_CLK_TCK);
322 
323     /**
324      * Convert the system time in clock ticks(clock_t type in times(), not in clock()) to
325      * milliseconds. times() clock_t ticks at the kernel's USER_HZ (100) while clock() clock_t
326      * ticks at CLOCKS_PER_SEC (1000000).
327      *
328      * See the NOTES on https://man7.org/linux/man-pages/man2/times.2.html for the difference
329      * of clock_t used in clock() and times().
330      */
ticksToMilliSeconds(int intClockTicks)331     public static long ticksToMilliSeconds(int intClockTicks) {
332         final long longClockTicks = intClockTicks & 0xffffffffL;
333         return (longClockTicks * 1000) / CLOCK_TICKS_PER_SECOND;
334     }
335 
NetlinkUtils()336     private NetlinkUtils() {}
337 
getAndProcessNetlinkDumpMessagesWithFd( FileDescriptor fd, byte[] dumpRequestMessage, int nlFamily, Class<T> msgClass, Consumer<T> func)338     private static <T extends NetlinkMessage> void getAndProcessNetlinkDumpMessagesWithFd(
339             FileDescriptor fd, byte[] dumpRequestMessage, int nlFamily, Class<T> msgClass,
340             Consumer<T> func)
341             throws SocketException, InterruptedIOException, ErrnoException {
342         // connecToKernel throws ErrnoException and SocketException, should be handled by caller
343         connectToKernel(fd);
344 
345         // sendMessage throws InterruptedIOException and ErrnoException,
346         // should be handled by caller
347         sendMessage(fd, dumpRequestMessage, 0, dumpRequestMessage.length, IO_TIMEOUT_MS);
348 
349         while (true) {
350             // recvMessage throws ErrnoException, InterruptedIOException
351             // should be handled by caller
352             final ByteBuffer buf = recvMessage(
353                     fd, NetlinkUtils.DEFAULT_RECV_BUFSIZE, IO_TIMEOUT_MS);
354 
355             while (buf.remaining() > 0) {
356                 final int position = buf.position();
357                 final NetlinkMessage nlMsg = NetlinkMessage.parse(buf, nlFamily);
358                 if (nlMsg == null) {
359                     // Move to the position where parse started for error log.
360                     buf.position(position);
361                     Log.e(TAG, "Failed to parse netlink message: " + hexify(buf));
362                     break;
363                 }
364 
365                 if (nlMsg.getHeader().nlmsg_type == NLMSG_DONE) {
366                     return;
367                 }
368 
369                 if (!msgClass.isInstance(nlMsg)) {
370                     Log.wtf(TAG, "Received unexpected netlink message: " + nlMsg);
371                     continue;
372                 }
373 
374                 final T msg = (T) nlMsg;
375                 func.accept(msg);
376             }
377         }
378     }
379     /**
380      * Sends a netlink dump request and processes the returned dump messages
381      *
382      * @param <T> extends NetlinkMessage
383      * @param dumpRequestMessage netlink dump request message to be sent
384      * @param nlFamily netlink family
385      * @param msgClass expected class of the netlink message
386      * @param func function defined by caller to handle the dump messages
387      * @throws SocketException when fails to connect socket to kernel
388      * @throws InterruptedIOException when fails to read the dumpFd
389      * @throws ErrnoException when fails to create dump fd, send dump request
390      *                        or receive messages
391      */
getAndProcessNetlinkDumpMessages( byte[] dumpRequestMessage, int nlFamily, Class<T> msgClass, Consumer<T> func)392     public static <T extends NetlinkMessage> void getAndProcessNetlinkDumpMessages(
393             byte[] dumpRequestMessage, int nlFamily, Class<T> msgClass,
394             Consumer<T> func)
395             throws SocketException, InterruptedIOException, ErrnoException {
396         // Create socket
397         final FileDescriptor fd = netlinkSocketForProto(nlFamily, SOCKET_DUMP_RECV_BUFSIZE);
398         try {
399             getAndProcessNetlinkDumpMessagesWithFd(fd, dumpRequestMessage, nlFamily,
400                     msgClass, func);
401         } finally {
402             closeSocketQuietly(fd);
403         }
404     }
405 
406     /**
407      * Construct a RTM_GETROUTE message for dumping multicast IPv6 routes from kernel.
408      */
newIpv6MulticastRouteDumpRequest()409     private static byte[] newIpv6MulticastRouteDumpRequest() {
410         final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
411         nlmsghdr.nlmsg_type = NetlinkConstants.RTM_GETROUTE;
412         nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
413         final short shortZero = 0;
414 
415         // family must be RTNL_FAMILY_IP6MR to dump IPv6 multicast routes.
416         // dstLen, srcLen, tos and scope must be zero in FIB dump request.
417         // protocol, flags must be 0, and type must be RTN_MULTICAST (if not 0) for multicast
418         // dump request.
419         // table or RTA_TABLE attributes can be used to dump a specific routing table.
420         // RTA_OIF attribute can be used to dump only routes containing given oif.
421         // Here no attributes are set so the kernel can return all multicast routes.
422         final StructRtMsg rtMsg =
423                 new StructRtMsg(RTNL_FAMILY_IP6MR /* family */, shortZero /* dstLen */,
424                         shortZero /* srcLen */, shortZero /* tos */, shortZero /* table */,
425                         shortZero /* protocol */, shortZero /* scope */, shortZero /* type */,
426                         0L /* flags */);
427         final RtNetlinkRouteMessage msg =
428             new RtNetlinkRouteMessage(nlmsghdr, rtMsg);
429 
430         final int spaceRequired = StructNlMsgHdr.STRUCT_SIZE + StructRtMsg.STRUCT_SIZE;
431         nlmsghdr.nlmsg_len = spaceRequired;
432         final byte[] bytes = new byte[NetlinkConstants.alignedLengthOf(spaceRequired)];
433         final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
434         byteBuffer.order(ByteOrder.nativeOrder());
435         msg.pack(byteBuffer);
436         return bytes;
437      }
438 
439     /**
440      * Get the list of IPv6 multicast route messages from kernel.
441      */
getIpv6MulticastRoutes()442     public static List<RtNetlinkRouteMessage> getIpv6MulticastRoutes() {
443         final byte[] dumpMsg = newIpv6MulticastRouteDumpRequest();
444         List<RtNetlinkRouteMessage> routes = new ArrayList<>();
445         Consumer<RtNetlinkRouteMessage> handleNlDumpMsg = (msg) -> {
446             if (msg.getRtmFamily() == RTNL_FAMILY_IP6MR) {
447                 // Sent rtmFamily RTNL_FAMILY_IP6MR in dump request to make sure ipv6
448                 // multicast routes are included in netlink reply messages, the kernel
449                 // may also reply with other kind of routes, so we filter them out here.
450                 routes.add(msg);
451             }
452         };
453         try {
454             NetlinkUtils.<RtNetlinkRouteMessage>getAndProcessNetlinkDumpMessages(
455                     dumpMsg, NETLINK_ROUTE, RtNetlinkRouteMessage.class,
456                     handleNlDumpMsg);
457         } catch (SocketException | InterruptedIOException | ErrnoException e) {
458             Log.e(TAG, "Failed to dump multicast routes");
459             return routes;
460         }
461 
462         return routes;
463     }
464 
closeSocketQuietly(final FileDescriptor fd)465     private static void closeSocketQuietly(final FileDescriptor fd) {
466         try {
467             SocketUtils.closeSocket(fd);
468         } catch (IOException e) {
469             // Nothing we can do here
470         }
471     }
472 }
473