/*
 * 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 android.net.ip;

import static android.system.OsConstants.AF_INET6;
import static android.system.OsConstants.AF_PACKET;
import static android.system.OsConstants.ENODEV;
import static android.system.OsConstants.ETH_P_IPV6;
import static android.system.OsConstants.IPPROTO_RAW;
import static android.system.OsConstants.SOCK_DGRAM;
import static android.system.OsConstants.SOCK_NONBLOCK;
import static android.system.OsConstants.SOCK_RAW;

import static com.android.net.module.util.SocketUtils.closeSocketQuietly;

import android.net.util.SocketUtils;
import android.os.Handler;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;

import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.PacketReader;
import com.android.networkstack.tethering.util.TetheringUtils;

import java.io.FileDescriptor;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Arrays;

/**
 * Basic IPv6 Neighbor Advertisement Forwarder.
 *
 * Forward NA packets from upstream iface to tethered iface
 * and NS packets from tethered iface to upstream iface.
 *
 * @hide
 */
public class NeighborPacketForwarder extends PacketReader {
    private final String mTag;

    private FileDescriptor mFd;

    // TODO: get these from NetworkStackConstants.
    private static final int IPV6_ADDR_LEN = 16;
    private static final int IPV6_DST_ADDR_OFFSET = 24;
    private static final int IPV6_HEADER_LEN = 40;
    private static final int ETH_HEADER_LEN = 14;

    private InterfaceParams mListenIfaceParams, mSendIfaceParams;

    private final int mType;
    public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT  = 136;
    public static final int ICMPV6_NEIGHBOR_SOLICITATION = 135;

    public NeighborPacketForwarder(Handler h, InterfaceParams tetheredInterface, int type) {
        super(h);
        mTag = NeighborPacketForwarder.class.getSimpleName() + "-"
                + tetheredInterface.name + "-" + type;
        mType = type;

        if (mType == ICMPV6_NEIGHBOR_ADVERTISEMENT) {
            mSendIfaceParams = tetheredInterface;
        } else {
            mListenIfaceParams = tetheredInterface;
        }
    }

    /** Set new upstream iface and start/stop based on new params. */
    public void setUpstreamIface(InterfaceParams upstreamParams) {
        final InterfaceParams oldUpstreamParams;

        if (mType == ICMPV6_NEIGHBOR_ADVERTISEMENT) {
            oldUpstreamParams = mListenIfaceParams;
            mListenIfaceParams = upstreamParams;
        } else {
            oldUpstreamParams = mSendIfaceParams;
            mSendIfaceParams = upstreamParams;
        }

        if (oldUpstreamParams == null && upstreamParams != null) {
            start();
        } else if (oldUpstreamParams != null && upstreamParams == null) {
            stop();
        } else if (oldUpstreamParams != null && upstreamParams != null
                   && oldUpstreamParams.index != upstreamParams.index) {
            stop();
            start();
        }
    }

    @Override
    protected FileDescriptor createFd() {
        try {
            // ICMPv6 packets from modem do not have eth header, so RAW socket cannot be used.
            // To keep uniformity in both directions PACKET socket can be used.
            mFd = Os.socket(AF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, 0);

            // TODO: convert setup*Socket to setupICMPv6BpfFilter with filter type?
            if (mType == ICMPV6_NEIGHBOR_ADVERTISEMENT) {
                TetheringUtils.setupNaSocket(mFd);
            } else if (mType == ICMPV6_NEIGHBOR_SOLICITATION) {
                TetheringUtils.setupNsSocket(mFd);
            }

            SocketAddress bindAddress = SocketUtils.makePacketSocketAddress(
                                                        ETH_P_IPV6, mListenIfaceParams.index);
            Os.bind(mFd, bindAddress);
        } catch (ErrnoException | SocketException e) {
            // An ENODEV(No such device) will rise if tethering stopped before this function, this
            // may happen when enable/disable tethering quickly.
            if (e instanceof ErrnoException && ((ErrnoException) e).errno == ENODEV) {
                Log.w(mTag, "Failed to create socket because tethered interface is gone", e);
            } else {
                Log.wtf(mTag, "Failed to create socket", e);
            }
            closeSocketQuietly(mFd);
            return null;
        }

        return mFd;
    }

    private Inet6Address getIpv6DestinationAddress(byte[] recvbuf) {
        Inet6Address dstAddr;
        try {
            dstAddr = (Inet6Address) Inet6Address.getByAddress(Arrays.copyOfRange(recvbuf,
                    IPV6_DST_ADDR_OFFSET, IPV6_DST_ADDR_OFFSET + IPV6_ADDR_LEN));
        } catch (UnknownHostException | ClassCastException impossible) {
            throw new AssertionError("16-byte array not valid IPv6 address?");
        }
        return dstAddr;
    }

    @Override
    protected void handlePacket(byte[] recvbuf, int length) {
        if (mSendIfaceParams == null) {
            return;
        }

        // The BPF filter should already have checked the length of the packet, but...
        if (length < IPV6_HEADER_LEN) {
            return;
        }
        Inet6Address destv6 = getIpv6DestinationAddress(recvbuf);
        if (!destv6.isMulticastAddress()) {
            return;
        }
        InetSocketAddress dest = new InetSocketAddress(destv6, 0);

        FileDescriptor fd = null;
        try {
            fd = Os.socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_RAW);
            SocketUtils.bindSocketToInterface(fd, mSendIfaceParams.name);

            int ret = Os.sendto(fd, recvbuf, 0, length, 0, dest);
        } catch (ErrnoException | SocketException e) {
            Log.e(mTag, "handlePacket error: " + e);
        } finally {
            closeSocketQuietly(fd);
        }
    }
}
