1 /*
2  * Copyright (C) 2015 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.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.SOCK_DGRAM;
25 import static android.system.OsConstants.SOL_SOCKET;
26 import static android.system.OsConstants.SO_RCVBUF;
27 import static android.system.OsConstants.SO_RCVTIMEO;
28 import static android.system.OsConstants.SO_SNDTIMEO;
29 
30 import android.net.util.SocketUtils;
31 import android.system.ErrnoException;
32 import android.system.Os;
33 import android.system.StructTimeval;
34 import android.util.Log;
35 
36 import java.io.FileDescriptor;
37 import java.io.IOException;
38 import java.io.InterruptedIOException;
39 import java.net.SocketException;
40 import java.nio.ByteBuffer;
41 import java.nio.ByteOrder;
42 
43 
44 /**
45  * NetlinkSocket
46  *
47  * A small static class to assist with AF_NETLINK socket operations.
48  *
49  * @hide
50  */
51 public class NetlinkSocket {
52     private static final String TAG = "NetlinkSocket";
53 
54     public static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
55     public static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
56 
sendOneShotKernelMessage(int nlProto, byte[] msg)57     public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException {
58         final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage";
59         final long IO_TIMEOUT = 300L;
60 
61         final FileDescriptor fd = forProto(nlProto);
62 
63         try {
64             connectToKernel(fd);
65             sendMessage(fd, msg, 0, msg.length, IO_TIMEOUT);
66             final ByteBuffer bytes = recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT);
67             // recvMessage() guaranteed to not return null if it did not throw.
68             final NetlinkMessage response = NetlinkMessage.parse(bytes);
69             if (response != null && response instanceof NetlinkErrorMessage &&
70                     (((NetlinkErrorMessage) response).getNlMsgError() != null)) {
71                 final int errno = ((NetlinkErrorMessage) response).getNlMsgError().error;
72                 if (errno != 0) {
73                     // TODO: consider ignoring EINVAL (-22), which appears to be
74                     // normal when probing a neighbor for which the kernel does
75                     // not already have / no longer has a link layer address.
76                     Log.e(TAG, errPrefix + ", errmsg=" + response.toString());
77                     // Note: convert kernel errnos (negative) into userspace errnos (positive).
78                     throw new ErrnoException(response.toString(), Math.abs(errno));
79                 }
80             } else {
81                 final String errmsg;
82                 if (response == null) {
83                     bytes.position(0);
84                     errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
85                 } else {
86                     errmsg = response.toString();
87                 }
88                 Log.e(TAG, errPrefix + ", errmsg=" + errmsg);
89                 throw new ErrnoException(errmsg, EPROTO);
90             }
91         } catch (InterruptedIOException e) {
92             Log.e(TAG, errPrefix, e);
93             throw new ErrnoException(errPrefix, ETIMEDOUT, e);
94         } catch (SocketException e) {
95             Log.e(TAG, errPrefix, e);
96             throw new ErrnoException(errPrefix, EIO, e);
97         } finally {
98             try {
99                 SocketUtils.closeSocket(fd);
100             } catch (IOException e) {
101                 // Nothing we can do here
102             }
103         }
104     }
105 
forProto(int nlProto)106     public static FileDescriptor forProto(int nlProto) throws ErrnoException {
107         final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM, nlProto);
108         Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, SOCKET_RECV_BUFSIZE);
109         return fd;
110     }
111 
connectToKernel(FileDescriptor fd)112     public static void connectToKernel(FileDescriptor fd) throws ErrnoException, SocketException {
113         Os.connect(fd, makeNetlinkSocketAddress(0, 0));
114     }
115 
checkTimeout(long timeoutMs)116     private static void checkTimeout(long timeoutMs) {
117         if (timeoutMs < 0) {
118             throw new IllegalArgumentException("Negative timeouts not permitted");
119         }
120     }
121 
122     /**
123      * Wait up to |timeoutMs| (or until underlying socket error) for a
124      * netlink message of at most |bufsize| size.
125      *
126      * Multi-threaded calls with different timeouts will cause unexpected results.
127      */
recvMessage(FileDescriptor fd, int bufsize, long timeoutMs)128     public static ByteBuffer recvMessage(FileDescriptor fd, int bufsize, long timeoutMs)
129             throws ErrnoException, IllegalArgumentException, InterruptedIOException {
130         checkTimeout(timeoutMs);
131 
132         Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(timeoutMs));
133 
134         ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize);
135         int length = Os.read(fd, byteBuffer);
136         if (length == bufsize) {
137             Log.w(TAG, "maximum read");
138         }
139         byteBuffer.position(0);
140         byteBuffer.limit(length);
141         byteBuffer.order(ByteOrder.nativeOrder());
142         return byteBuffer;
143     }
144 
145     /**
146      * Send a message to a peer to which this socket has previously connected,
147      * waiting at most |timeoutMs| milliseconds for the send to complete.
148      *
149      * Multi-threaded calls with different timeouts will cause unexpected results.
150      */
sendMessage( FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs)151     public static int sendMessage(
152             FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs)
153             throws ErrnoException, IllegalArgumentException, InterruptedIOException {
154         checkTimeout(timeoutMs);
155         Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(timeoutMs));
156         return Os.write(fd, bytes, offset, count);
157     }
158 }
159