/* iface-bpf.cc
****************************************************************************************************************
****************************************************************************************************************

    Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2020, 2021, 2026 Askar
    Almatov

    This file is part of Trafficmeter.

    Trafficmeter is free software: you can redistribute it and/or modify it under the terms of the GNU General
    Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
    option) any later version.

    Trafficmeter is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
    implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
    License for more details.

    You should have received a copy of the GNU General Public License along with Trafficmeter. If not, see
    <http://www.gnu.org/licenses/>.
*/

#include <cassert>
#include <cerrno>
#include <iostream>
#include <stdexcept>
#include <fcntl.h>
#include <ifaddrs.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <net/bpf.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>

#include "iface.h"
#include "erspan.h"

using namespace trafficmeter;

using std::invalid_argument;
using std::runtime_error;
using std::string;
using std::to_string;

constexpr const unsigned    BUFFER_SIZE_ = 128U << 20;  // 128 MiB

/**************************************************************************************************************/
static unsigned
getMaxBufferSize_()
{
    unsigned    bufferSize;
    size_t      bufferSizeLength = sizeof( bufferSize );

    if ( sysctlbyname("net.bpf.maxbufsize", &bufferSize, &bufferSizeLength, 0, 0) )
    {
        sysctlbyname( "debug.bpf_maxbufsize", &bufferSize, &bufferSizeLength, 0, 0 );
    }

    return bufferSize;
}

/**************************************************************************************************************/
static void
setMaxBufferSize_( unsigned bufferSize )
{
    size_t  bufferSizeLength = sizeof( bufferSize );

    if ( sysctlbyname("net.bpf.maxbufsize", 0, 0, &bufferSize, bufferSizeLength) )
    {
        sysctlbyname( "debug.bpf_maxbufsize", 0, 0, &bufferSize, bufferSizeLength );
    }
}

/**************************************************************************************************************/
static unsigned
setBufferSize_( int fd, unsigned bufferSize )
{
    if ( bufferSize > getMaxBufferSize_() )
    {
        setMaxBufferSize_( bufferSize );
    }

    assert( ioctl(fd, BIOCSBLEN, &bufferSize) != -1 );
    assert( ioctl(fd, BIOCGBLEN, &bufferSize) != -1 );

    return bufferSize;
}

/**************************************************************************************************************/
static bool
bindInterface_( int fd, const string& name, bool isPromisc )
{
    struct ifreq  ifr;

    memset( &ifr, 0, sizeof(ifr) );
    strncpy( ifr.ifr_name, name.c_str(), sizeof(ifr.ifr_name) );

    if ( ioctl(fd, BIOCSETIF, &ifr) == -1 )
    {
        return false;
    }

    if ( isPromisc )
    {
        assert( ioctl(fd, BIOCPROMISC, 0) != -1 );
    }

    return true;
}

/**************************************************************************************************************/
static void
setReadTimeout_( int fd, int timeout )
{
    struct timeval  tv = { .tv_sec = 0, .tv_usec = timeout * 1000 };

    assert( ioctl(fd, BIOCSRTIMEOUT, &tv) != -1 );
}

/**************************************************************************************************************/
static string
getLinkType_( const string& name )
{
    string              linkType;
    struct ifaddrs*     ifaFirst;

    getifaddrs( &ifaFirst );

    for ( struct ifaddrs* ifa = ifaFirst; ifa != nullptr; ifa = ifa->ifa_next )
    {
        if ( ifa->ifa_addr->sa_family == AF_LINK && ifa->ifa_name == name )
        {
            const struct sockaddr_dl*  sdl = reinterpret_cast<struct sockaddr_dl*>( ifa->ifa_addr );

            //cout << "sdl_type=0x" << hex << static_cast<unsigned>( sdl.sdl_type ) << dec << endl;

            if ( sdl->sdl_type == IFT_ETHER && sdl->sdl_alen == ETHER_ADDR_LEN )
            {
                linkType = "Ethernet";
            }
            else if ( sdl->sdl_type == IFT_L2VLAN && sdl->sdl_alen == ETHER_ADDR_LEN )
            {
                linkType = "Ethernet 802.1Q";
            }
            else if ( sdl->sdl_type == IFT_LOOP )
            {
                linkType = "Loopback";
            }

            break;
        }
    }

    freeifaddrs( ifaFirst );
    return std::move( linkType );
}

/**************************************************************************************************************/
static bool
isPromisc_( const string& name )
{
    bool                isPromisc = false;
    struct ifaddrs*     ifaFirst;

    getifaddrs( &ifaFirst );

    for ( struct ifaddrs* ifa = ifaFirst; ifa != nullptr; ifa = ifa->ifa_next )
    {
        if ( ifa->ifa_addr->sa_family == AF_LINK && ifa->ifa_name == name )
        {
            isPromisc = ifa->ifa_flags & IFF_PROMISC;
            break;
        }
    }

    freeifaddrs( ifaFirst );
    return isPromisc;
}

/**************************************************************************************************************/
static int
getMtu_( const string& name )
{
    int             fd = socket( AF_INET, SOCK_DGRAM, 0 );
    struct ifreq    ifr;

    assert( fd != -1 );
    memset( &ifr, 0, sizeof(ifr) );
    strncpy( ifr.ifr_name, name.c_str(), sizeof(ifr.ifr_name) );
    ifr.ifr_addr.sa_family = AF_INET;
    assert( ioctl(fd, SIOCGIFMTU, &ifr) != -1 );
    close( fd );
    return ifr.ifr_mtu;
}

/**************************************************************************************************************/
Iface::Iface( const string& name, bool isPromisc ) :
    Source( name ),
    buffer_( nullptr ),
    bufferSize_( BUFFER_SIZE_ ),
    record_( nullptr ),
    recordsEnd_( nullptr )
{
    if ( name_.size() > IFNAMSIZ )
    {
        throw invalid_argument( "interface name is too long" );
    }

    if ( if_nametoindex(name.c_str()) == 0 )
    {
        throw runtime_error( "interface does not exist" );
    }

    for ( int i=0;; i++ )
    {
        string  device( "/dev/bpf" + to_string(i) );

        fd_ = ::open( device.c_str(), O_RDONLY );

        if ( fd_ >= 0 )
        {
            device_ = device;
            break;
        }
        else if ( errno != EBUSY )
        {
            throw runtime_error( "there is no available bpf devices or you have no rights" );
        }
    }

    bufferSize_ = setBufferSize_( fd_, bufferSize_ );
    setReadTimeout_( fd_, SOURCE_TIMEOUT );
    buffer_ = record_ = recordsEnd_ = new uint8_t[ bufferSize_ ];

    if ( !bindInterface_(fd_, name_, isPromisc) )
    {
        ::close( fd_ );
        throw runtime_error( "interface is down or invalid" );
    }

    linkType_ = getLinkType_( name_ );

    info_ =
    (
        device_ + " " +
        linkType_ + ( linkType_.empty()? "" : " " ) +
        ( isPromisc_( name_ )? "promisc " : "" ) +
        ( shouldCheckErspan_? "erspan " : "" ) +
        "mtu=" + to_string( getMtu_(name_) ) + " " +
        "buf=" + to_string( bufferSize_ >> 20 ) + "MiB " +
        __FILE__
    );
}

/**************************************************************************************************************/
Iface::~Iface()
{
    if ( buffer_ != nullptr )
    {
        delete[] buffer_;
    }

    if ( fd_ >= 0 )
    {
        ::close( fd_ );
    }
}

/**************************************************************************************************************/
void
Iface::open()
{
    assert( ioctl(fd_,BIOCFLUSH) >= 0 );
}

/**************************************************************************************************************/
void
Iface::close()
{
}

/**************************************************************************************************************/
bool
Iface::checkForRecords()
{
    if ( record_ < recordsEnd_ )
    {
        return true;
    }

    long    nRead = read( fd_, buffer_, bufferSize_ );

    if ( nRead <= 0 )
    {
        return false;
    }

    struct bpf_stat     statistics;

    assert( ioctl(fd_,BIOCGSTATS,&statistics) >= 0 );
    nDropped_a_ += ( statistics.bs_drop - nDropped_a_ ) & 0xffffffffULL;
    record_ = buffer_;
    recordsEnd_ = buffer_ + nRead;
    return true;
}

/**************************************************************************************************************/
bool
Iface::copyRecords( uint8_t** destination, const uint8_t* destinationEnd )
{
    unsigned    nBytesToCopy = 0;
    unsigned    nRecordsToCopy = 0;
    uint8_t*    record = record_;
    bool        hasRecordsLeft = false;

    while ( record < recordsEnd_ )
    {
        const auto*     header = reinterpret_cast<const struct bpf_hdr*>( record );
        unsigned        recordSize = BPF_WORDALIGN( header->bh_caplen + header->bh_hdrlen );

        if ( *destination + nBytesToCopy + recordSize > destinationEnd )
        {
            hasRecordsLeft = true;
            break;
        }

        nBytesToCopy += recordSize;
        ++nRecordsToCopy;
        record += recordSize;
    }

    memcpy( *destination, record_, nBytesToCopy );
    nReceived_a_ += nRecordsToCopy;
    record_ = record;
    *destination += nBytesToCopy;
    return hasRecordsLeft;
}

/**************************************************************************************************************/
Source::Packet
Iface::getPacket()
{
    const auto*     header = reinterpret_cast<const struct bpf_hdr*>( record_ );
    Timestamp       timestamp( header->bh_tstamp.tv_sec, header->bh_tstamp.tv_usec * 1000 );
    const uint8_t*  data2 = record_ + header->bh_hdrlen;
    unsigned        nBytes2 = header->bh_datalen;

    if ( shouldCheckErspan_ )
    {
        Erspan  erspan( data2, nBytes2 );

        data2 = erspan.data2();
        nBytes2 = erspan.nBytes2();
    }

    ++nReceived_a_;
    record_ += BPF_WORDALIGN( header->bh_caplen + header->bh_hdrlen );
    return { timestamp, data2, nBytes2 };
}

/**************************************************************************************************************/
Source::Packet
Iface::getPacketFromCopy( uint8_t** record ) const
{
    const auto*     header = reinterpret_cast<const struct bpf_hdr*>( *record );
    Timestamp       timestamp( header->bh_tstamp.tv_sec, header->bh_tstamp.tv_usec * 1000 );
    const uint8_t*  data2 = *record + header->bh_hdrlen;
    unsigned        nBytes2 = header->bh_datalen;

    if ( shouldCheckErspan_ )
    {
        Erspan  erspan( data2, nBytes2 );

        data2 = erspan.data2();
        nBytes2 = erspan.nBytes2();
    }

    *record += BPF_WORDALIGN( header->bh_caplen + header->bh_hdrlen );
    return { timestamp, data2, nBytes2 };
}

/**************************************************************************************************************/
bool
Iface::isEthernet() const
{
    return linkType_.find( "Ethernet" ) != string::npos;
}
