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