/*
 * 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.connectToKernel(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) { }
}
