summaryrefslogtreecommitdiff
path: root/common/device/com/android/net/module/util/ip/NetlinkMonitor.java
blob: f882483da812724032ea35218bde4c345e29c0ae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.net.module.util.ip;

import static android.system.OsConstants.AF_NETLINK;
import static android.system.OsConstants.ENOBUFS;
import static android.system.OsConstants.SOCK_DGRAM;
import static android.system.OsConstants.SOCK_NONBLOCK;
import static android.system.OsConstants.SOL_SOCKET;
import static android.system.OsConstants.SO_RCVBUF;

import static com.android.net.module.util.SocketUtils.closeSocketQuietly;
import static com.android.net.module.util.SocketUtils.makeNetlinkSocketAddress;
import static com.android.net.module.util.netlink.NetlinkConstants.hexify;

import android.annotation.NonNull;
import android.os.Handler;
import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;

import com.android.net.module.util.PacketReader;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.netlink.NetlinkErrorMessage;
import com.android.net.module.util.netlink.NetlinkMessage;
import com.android.net.module.util.netlink.NetlinkUtils;

import java.io.FileDescriptor;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * A simple base class to listen for netlink broadcasts.
 *
 * Opens a netlink socket of the given family and binds to the specified groups. Polls the socket
 * from the event loop of the passed-in {@link Handler}, and calls the subclass-defined
 * {@link #processNetlinkMessage} method on the handler thread for each netlink message that
 * arrives. Currently ignores all netlink errors.
 * @hide
 */
public class NetlinkMonitor extends PacketReader {
    protected final SharedLog mLog;
    protected final String mTag;
    private final int mFamily;
    private final int mBindGroups;
    private final int mSockRcvbufSize;

    private static final boolean DBG = false;

    // Default socket receive buffer size. This means the specific buffer size is not set.
    private static final int DEFAULT_SOCKET_RECV_BUFSIZE = -1;

    /**
     * Constructs a new {@code NetlinkMonitor} instance.
     *
     * @param h The Handler on which to poll for messages and on which to call
     *          {@link #processNetlinkMessage}.
     * @param log A SharedLog to log to.
     * @param tag The log tag to use for log messages.
     * @param family the Netlink socket family to, e.g., {@code NETLINK_ROUTE}.
     * @param bindGroups the netlink groups to bind to.
     * @param sockRcvbufSize the specific socket receive buffer size in bytes. -1 means that don't
     *        set the specific socket receive buffer size in #createFd and use the default value in
     *        /proc/sys/net/core/rmem_default file. See SO_RCVBUF in man-pages/socket.
     */
    public NetlinkMonitor(@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag,
            int family, int bindGroups, int sockRcvbufSize) {
        super(h, NetlinkUtils.DEFAULT_RECV_BUFSIZE);
        mLog = log.forSubComponent(tag);
        mTag = tag;
        mFamily = family;
        mBindGroups = bindGroups;
        mSockRcvbufSize = sockRcvbufSize;
    }

    public NetlinkMonitor(@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag,
            int family, int bindGroups) {
        this(h, log, tag, family, bindGroups, DEFAULT_SOCKET_RECV_BUFSIZE);
    }

    @Override
    protected FileDescriptor createFd() {
        FileDescriptor fd = null;

        try {
            fd = Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_NONBLOCK, mFamily);
            if (mSockRcvbufSize != DEFAULT_SOCKET_RECV_BUFSIZE) {
                try {
                    Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, mSockRcvbufSize);
                } catch (ErrnoException e) {
                    Log.wtf(mTag, "Failed to set SO_RCVBUF to " + mSockRcvbufSize, e);
                }
            }
            Os.bind(fd, makeNetlinkSocketAddress(0, mBindGroups));
            NetlinkUtils.connectSocketToNetlink(fd);

            if (DBG) {
                final SocketAddress nlAddr = Os.getsockname(fd);
                Log.d(mTag, "bound to sockaddr_nl{" + nlAddr.toString() + "}");
            }
        } catch (ErrnoException | SocketException e) {
            logError("Failed to create rtnetlink socket", e);
            closeSocketQuietly(fd);
            return null;
        }

        return fd;
    }

    @Override
    protected void handlePacket(byte[] recvbuf, int length) {
        final long whenMs = SystemClock.elapsedRealtime();
        final ByteBuffer byteBuffer = ByteBuffer.wrap(recvbuf, 0, length);
        byteBuffer.order(ByteOrder.nativeOrder());

        while (byteBuffer.remaining() > 0) {
            try {
                final int position = byteBuffer.position();
                final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer, mFamily);
                if (nlMsg == null || nlMsg.getHeader() == null) {
                    byteBuffer.position(position);
                    mLog.e("unparsable netlink msg: " + hexify(byteBuffer));
                    break;
                }

                if (nlMsg instanceof NetlinkErrorMessage) {
                    mLog.e("netlink error: " + nlMsg);
                    continue;
                }

                processNetlinkMessage(nlMsg, whenMs);
            } catch (Exception e) {
                mLog.e("Error handling netlink message", e);
            }
        }
    }

    @Override
    protected void logError(String msg, Exception e) {
        mLog.e(msg, e);
    }

    // Ignoring ENOBUFS may miss any important netlink messages, there are some messages which
    // cannot be recovered by dumping current state once missed since kernel doesn't keep state
    // for it. In addition, dumping current state will not result in any RTM_DELxxx messages, so
    // reconstructing current state from a dump will be difficult. However, for those netlink
    // messages don't cause any state changes, e.g. RTM_NEWLINK with current link state, maybe
    // it's okay to ignore them, because these netlink messages won't cause any changes on the
    // LinkProperties. Given the above trade-offs, try to ignore ENOBUFS and that's similar to
    // what netd does today.
    //
    // TODO: log metrics when ENOBUFS occurs, or even force a disconnect, it will help see how
    // often this error occurs on fields with the associated socket receive buffer size.
    @Override
    protected boolean handleReadError(ErrnoException e) {
        logError("readPacket error: ", e);
        if (e.errno == ENOBUFS) {
            Log.wtf(mTag, "Errno: ENOBUFS");
            return false;
        }
        return true;
    }

    /**
     * Processes one netlink message. Must be overridden by subclasses.
     * @param nlMsg the message to process.
     * @param whenMs the timestamp, as measured by {@link SystemClock#elapsedRealtime}, when the
     *               message was received.
     */
    protected void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) { }
}