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.SOCK_CLOEXEC; 26 import static android.system.OsConstants.SOCK_DGRAM; 27 import static android.system.OsConstants.SOL_SOCKET; 28 import static android.system.OsConstants.SO_RCVBUF; 29 import static android.system.OsConstants.SO_RCVTIMEO; 30 import static android.system.OsConstants.SO_SNDTIMEO; 31 32 import android.net.util.SocketUtils; 33 import android.system.ErrnoException; 34 import android.system.Os; 35 import android.system.StructTimeval; 36 import android.util.Log; 37 38 import androidx.annotation.NonNull; 39 import androidx.annotation.Nullable; 40 41 import java.io.FileDescriptor; 42 import java.io.IOException; 43 import java.io.InterruptedIOException; 44 import java.net.SocketException; 45 import java.nio.ByteBuffer; 46 import java.nio.ByteOrder; 47 48 /** 49 * Utilities for netlink related class that may not be able to fit into a specific class. 50 * @hide 51 */ 52 public class NetlinkUtils { 53 private static final String TAG = "NetlinkUtils"; 54 /** Corresponds to enum from bionic/libc/include/netinet/tcp.h. */ 55 private static final int TCP_ESTABLISHED = 1; 56 private static final int TCP_SYN_SENT = 2; 57 private static final int TCP_SYN_RECV = 3; 58 59 public static final int TCP_ALIVE_STATE_FILTER = 60 (1 << TCP_ESTABLISHED) | (1 << TCP_SYN_SENT) | (1 << TCP_SYN_RECV); 61 62 public static final int UNKNOWN_MARK = 0xffffffff; 63 public static final int NULL_MASK = 0; 64 65 // Initial mark value corresponds to the initValue in system/netd/include/Fwmark.h. 66 public static final int INIT_MARK_VALUE = 0; 67 // Corresponds to enum definition in bionic/libc/kernel/uapi/linux/inet_diag.h 68 public static final int INET_DIAG_INFO = 2; 69 public static final int INET_DIAG_MARK = 15; 70 71 public static final long IO_TIMEOUT_MS = 300L; 72 73 public static final int DEFAULT_RECV_BUFSIZE = 8 * 1024; 74 public static final int SOCKET_RECV_BUFSIZE = 64 * 1024; 75 76 /** 77 * Return whether the input ByteBuffer contains enough remaining bytes for 78 * {@code StructNlMsgHdr}. 79 */ enoughBytesRemainForValidNlMsg(@onNull final ByteBuffer bytes)80 public static boolean enoughBytesRemainForValidNlMsg(@NonNull final ByteBuffer bytes) { 81 return bytes.remaining() >= StructNlMsgHdr.STRUCT_SIZE; 82 } 83 84 /** 85 * Parse netlink error message 86 * 87 * @param bytes byteBuffer to parse netlink error message 88 * @return NetlinkErrorMessage if bytes contains valid NetlinkErrorMessage, else {@code null} 89 */ 90 @Nullable parseNetlinkErrorMessage(ByteBuffer bytes)91 private static NetlinkErrorMessage parseNetlinkErrorMessage(ByteBuffer bytes) { 92 final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(bytes); 93 if (nlmsghdr == null || nlmsghdr.nlmsg_type != NetlinkConstants.NLMSG_ERROR) { 94 return null; 95 } 96 return NetlinkErrorMessage.parse(nlmsghdr, bytes); 97 } 98 99 /** 100 * Receive netlink ack message and check error 101 * 102 * @param fd fd to read netlink message 103 */ receiveNetlinkAck(final FileDescriptor fd)104 public static void receiveNetlinkAck(final FileDescriptor fd) 105 throws InterruptedIOException, ErrnoException { 106 final ByteBuffer bytes = recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT_MS); 107 // recvMessage() guaranteed to not return null if it did not throw. 108 final NetlinkErrorMessage response = parseNetlinkErrorMessage(bytes); 109 if (response != null && response.getNlMsgError() != null) { 110 final int errno = response.getNlMsgError().error; 111 if (errno != 0) { 112 // TODO: consider ignoring EINVAL (-22), which appears to be 113 // normal when probing a neighbor for which the kernel does 114 // not already have / no longer has a link layer address. 115 Log.e(TAG, "receiveNetlinkAck, errmsg=" + response.toString()); 116 // Note: convert kernel errnos (negative) into userspace errnos (positive). 117 throw new ErrnoException(response.toString(), Math.abs(errno)); 118 } 119 } else { 120 final String errmsg; 121 if (response == null) { 122 bytes.position(0); 123 errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes); 124 } else { 125 errmsg = response.toString(); 126 } 127 Log.e(TAG, "receiveNetlinkAck, errmsg=" + errmsg); 128 throw new ErrnoException(errmsg, EPROTO); 129 } 130 } 131 132 /** 133 * Send one netlink message to kernel via netlink socket. 134 * 135 * @param nlProto netlink protocol type. 136 * @param msg the raw bytes of netlink message to be sent. 137 */ sendOneShotKernelMessage(int nlProto, byte[] msg)138 public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException { 139 final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage"; 140 final FileDescriptor fd = netlinkSocketForProto(nlProto); 141 142 try { 143 connectSocketToNetlink(fd); 144 sendMessage(fd, msg, 0, msg.length, IO_TIMEOUT_MS); 145 receiveNetlinkAck(fd); 146 } catch (InterruptedIOException e) { 147 Log.e(TAG, errPrefix, e); 148 throw new ErrnoException(errPrefix, ETIMEDOUT, e); 149 } catch (SocketException e) { 150 Log.e(TAG, errPrefix, e); 151 throw new ErrnoException(errPrefix, EIO, e); 152 } finally { 153 try { 154 SocketUtils.closeSocket(fd); 155 } catch (IOException e) { 156 // Nothing we can do here 157 } 158 } 159 } 160 161 /** 162 * Create netlink socket with the given netlink protocol type. 163 * 164 * @return fd the fileDescriptor of the socket. 165 * @throws ErrnoException if the FileDescriptor not connect to be created successfully 166 */ netlinkSocketForProto(int nlProto)167 public static FileDescriptor netlinkSocketForProto(int nlProto) throws ErrnoException { 168 final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM, nlProto); 169 Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, SOCKET_RECV_BUFSIZE); 170 return fd; 171 } 172 173 /** 174 * Construct a netlink inet_diag socket. 175 */ createNetLinkInetDiagSocket()176 public static FileDescriptor createNetLinkInetDiagSocket() throws ErrnoException { 177 return Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_INET_DIAG); 178 } 179 180 /** 181 * Connect the given file descriptor to the Netlink interface to the kernel. 182 * 183 * The fd must be a SOCK_DGRAM socket : create it with {@link #netlinkSocketForProto} 184 * 185 * @throws ErrnoException if the {@code fd} could not connect to kernel successfully 186 * @throws SocketException if there is an error accessing a socket. 187 */ connectSocketToNetlink(FileDescriptor fd)188 public static void connectSocketToNetlink(FileDescriptor fd) 189 throws ErrnoException, SocketException { 190 Os.connect(fd, makeNetlinkSocketAddress(0, 0)); 191 } 192 checkTimeout(long timeoutMs)193 private static void checkTimeout(long timeoutMs) { 194 if (timeoutMs < 0) { 195 throw new IllegalArgumentException("Negative timeouts not permitted"); 196 } 197 } 198 199 /** 200 * Wait up to |timeoutMs| (or until underlying socket error) for a 201 * netlink message of at most |bufsize| size. 202 * 203 * Multi-threaded calls with different timeouts will cause unexpected results. 204 */ recvMessage(FileDescriptor fd, int bufsize, long timeoutMs)205 public static ByteBuffer recvMessage(FileDescriptor fd, int bufsize, long timeoutMs) 206 throws ErrnoException, IllegalArgumentException, InterruptedIOException { 207 checkTimeout(timeoutMs); 208 209 Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(timeoutMs)); 210 211 final ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize); 212 final int length = Os.read(fd, byteBuffer); 213 if (length == bufsize) { 214 Log.w(TAG, "maximum read"); 215 } 216 byteBuffer.position(0); 217 byteBuffer.limit(length); 218 byteBuffer.order(ByteOrder.nativeOrder()); 219 return byteBuffer; 220 } 221 222 /** 223 * Send a message to a peer to which this socket has previously connected. 224 * 225 * This waits at most |timeoutMs| milliseconds for the send to complete, will get the exception 226 * if it times out. 227 */ sendMessage( FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs)228 public static int sendMessage( 229 FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs) 230 throws ErrnoException, IllegalArgumentException, InterruptedIOException { 231 checkTimeout(timeoutMs); 232 Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(timeoutMs)); 233 return Os.write(fd, bytes, offset, count); 234 } 235 NetlinkUtils()236 private NetlinkUtils() {} 237 } 238